[
  {
    "path": ".dockerignore",
    "content": ".circleci/\n.idea/\n.git/\nbuild-aux/\nbuild-output/\n!build-output/version.txt\n!build-output/helm-version.txt\nintegration_test/\nk8s/\npackaging/\ntest-infra/\ntools/\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.go]\nindent_style = tab\n; mimic gofmt's go/printer.printer.funcBody().maxSize\nmax_line_length = 100\n; mimic gofmt's cmd/gofmt.tabWidth\ntab_width = 8\n\n[{go.mod,go.sum}]\nindent_style = tab\n\n[*.proto]\nindent_style = space\nindent_size = 2\n"
  },
  {
    "path": ".gitattributes",
    "content": "# Always check out with LF so that golangci-lint doesn't break\n*.go text eol=lf"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [thallgren]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Bug_report.md",
    "content": "---\r\nname: Bug report\r\nabout: Create a report to help us improve Telepresence\r\n\r\n---\r\n\r\n**Describe the bug**\r\nA clear and concise description of what the bug is.\r\n\r\nPlease use `telepresence loglevel debug` to ensure we have the most helpful logs,\r\nreproduce the error, and then run `telepresence gather-logs` to create a\r\nzip file of all logs for Telepresence's components (root and user daemons,\r\ntraffic-manager, and traffic-agents) and attach it to this issue. See an\r\nexample command below:\r\n```\r\ntelepresence loglevel debug\r\n\r\n* reproduce the error *\r\n\r\ntelepresence gather-logs --output-file /tmp/telepresence_logs.zip\r\n\r\n# To see all options, run the following command\r\ntelepresence gather-logs --help\r\n```\r\n\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n1. When I run '...'\r\n2. I see '....'\r\n3. So I look at '....'\r\n4. See error\r\n\r\n**Expected behavior**\r\nA clear and concise description of what you expected to happen.\r\n\r\n**Versions (please complete the following information):**\r\n - Output of `telepresence version` (preferably while telepresence is connected)\r\n - Operating system of workstation running `telepresence` commands\r\n - Kubernetes environment and Version [e.g. Minikube, bare metal, Google Kubernetes Engine]\r\n\r\n**Additional context**\r\nAdd any other context about the problem here.\r\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Feature_request.md",
    "content": "---\r\nname: Feature request\r\nabout: Suggest an idea for Telepresence\r\n\r\n---\r\n\r\n**Please describe your use case / problem.**\r\nA clear and concise description of your use case / problem. The \"why\" is really valuable to us. For example, \"I have a set of services whose clients have long-lived connections.\"\r\n\r\n**Describe the solution you'd like**\r\nA clear and concise description of what you want to happen.\r\n\r\n**Describe alternatives you've considered**\r\nA clear and concise description of any alternative solutions or features you've considered.\r\n\r\n**Versions (please complete the following information)**\r\n  - Output of `telepresence version` (just in case the feature exists in future versions)\r\n  - Kubernetes Environment and Version\r\n\r\n**Additional context**\r\nAdd any other context about the feature request here.\r\n"
  },
  {
    "path": ".github/actions/install-dependencies/action.yaml",
    "content": "name: \"Install dependencies\"\ndescription: \"Install dependencies required by the runner\"\nruns:\n  using: composite\n  steps:\n    - uses: actions/setup-go@v5\n      with:\n        go-version: stable\n    - if: runner.os == 'Linux'\n      name: install Linux dependencies\n      shell: bash\n      run: |\n        sudo rm -f /etc/apt/sources.list.d/google-chrome.list\n        sudo apt-get update\n        sudo apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu sshfs socat libfuse-dev make jq\n        sudo sh -c 'echo user_allow_other >> /etc/fuse.conf'\n    - if: runner.os == 'macOS'\n      name: install macOS dependencies\n      shell: bash\n      env:\n        HOMEBREW_NO_INSTALL_FROM_API: \"\"\n        HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: \"1\"\n      run: |\n        brew untap homebrew/core || :\n        brew untap homebrew/cask || :\n\n        # Remove Python3 symlinks in /usr/local/bin as workaround to brew update issues\n        # https://github.com/actions/setup-python/issues/577\n        rm /usr/local/bin/2to3* || :\n        rm /usr/local/bin/idle3* || :\n        rm /usr/local/bin/pydoc* || :\n        rm /usr/local/bin/python3* || :\n\n        brew update\n        brew install --cask macfuse\n        brew install gromgit/fuse/sshfs-mac\n        brew link --overwrite sshfs-mac\n\n        if [[ ${RUNNER_ARCH} == \"ARM64\" ]]; then\n          brew install jq\n        fi\n    - if: runner.os == 'Windows'\n      name: setup make\n      shell: pwsh\n      run: |\n        # Use make from Git for Windows (pre-installed on runners)\n        echo \"C:\\Program Files\\Git\\usr\\bin\" | Out-File -FilePath $env:GITHUB_PATH -Append\n    - if: runner.os == 'Windows'\n      name: download winfsp\n      uses: nick-fields/retry/@v3\n      with:\n        max_attempts: 3\n        timeout_minutes: 1\n        shell: bash\n        command: make winfsp.msi\n    - if: runner.os == 'Windows'\n      name: download sshfs\n      uses: nick-fields/retry/@v3\n      with:\n        max_attempts: 3\n        timeout_minutes: 1\n        shell: bash\n        command: make sshfs-win.msi\n    - if: runner.os == 'Windows'\n      name: download sshfs\n      uses: nick-fields/retry/@v3\n      with:\n        max_attempts: 3\n        timeout_minutes: 1\n        shell: bash\n        command: make wintun.dll\n    - if: runner.os == 'Windows'\n      name: install winfsp and sshfs\n      shell: powershell\n      run: |\n        Start-Process msiexec -Wait -verb runAs -Args \"/i build-output\\\\winfsp.msi /passive /qn /L*V winfsp-install.log\"\n        Start-Process msiexec -Wait -verb runAs -Args \"/i build-output\\\\sshfs-win.msi /passive /qn /L*V sshfs-win-install.log\"\n\n        [Environment]::SetEnvironmentVariable(\"Path\", \"C:\\\\;C:\\\\Program Files\\\\SSHFS-Win\\\\bin;$ENV:Path\", \"Machine\")\n"
  },
  {
    "path": ".github/actions/upload-logs/action.yaml",
    "content": "name: \"Upload logs\"\ndescription: \"Upload logs from the test run\"\nruns:\n  using: composite\n  steps:\n    - run: |\n        LOGS=\"test-logs/${{ runner.os }}/${{ runner.arch }}\"\n        mkdir -p \"$LOGS\"\n        if [[ $RUNNER_OS != Windows ]]; then\n          rsync -ma --include='*/' --include='*.tap' --include='*.log' --include='Test*.webm' --exclude='*' . \"$LOGS\"\n        fi\n        for file in \\\n          {\"${XDG_CACHE_HOME:-$HOME/.cache}/telepresence/logs\",\"$HOME/Library/Logs/telepresence\",\"$LOCALAPPDATA/telepresence/logs\",\".\"}/*.log\n        do\n          if [ -s \"$file\" ]; then\n            cp -v \"$file\" \"$LOGS\" || true\n          fi\n        done\n      shell: bash\n      name: Gather logs\n    - name: Upload logs\n      uses: actions/upload-artifact@v4\n      with:\n        # If an environment variable LOG_SUFFIX is set, it will be appended to the log filename.\n        name: ${{github.job}}-logs-${{ env.LOG_SUFFIX }}\n        path: |\n          test-logs/${{ runner.os }}/${{ runner.arch }}/*\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Description\n\nA few sentences describing the overall goals of the pull request's commits.\n\n## Checklist\n\n<!--\n  Please review the requirements for each checkbox, and check them\n  off (change \"[ ]\" to \"[x]\") as you verify that they are complete.\n-->\n\n - [ ] I made sure to update `./CHANGELOG.yml` (our release notes are generated from this file).\n - [ ] I made sure to add any documentation changes required for my change.\n - [ ] My change is adequately tested.\n - [ ] I updated `CONTRIBUTING.md` with any special dev tricks I had to use to work on this code efficiently.\n - [ ] Once my PR is ready to have integration tests ran, I posted the PR in #telepresence-oss channel on the\n       [CNCF Slack](https://slack.cncf.io/) so that the \"ok to test\" label can be applied."
  },
  {
    "path": ".github/workflows/dev.yaml",
    "content": "name: \"Integration Tests\"\non:\n  pull_request:\n    types:\n      - labeled\n\npermissions:\n  contents: read\n  pull-requests: write\n\nenv:\n  TELEPRESENCE_REGISTRY: local\n\njobs:\n  build_and_test:\n    if: ${{ github.event.label.name == 'ok to test' || github.event.label.name == 'compatibility test' }}\n    strategy:\n      fail-fast: false\n      matrix:\n        runners:\n          - ubuntu-latest\n          # Re-enable when we can run a proper cluster. Colima almost works on macOS but the very limited\n          # resources available make the test fail very often. On windows, we'll need WSL2\n          # - macos-latest\n          # - windows-latest\n    runs-on: ${{ matrix.runners }}\n    steps:\n      - name: Remove ok to test label\n        if: github.event.label.name == 'ok to test'\n        uses: buildsville/add-remove-label@v2.0.1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          labels: ok to test\n          type: remove\n      - name: Remove compatibility test label\n        if: github.event.label.name == 'compatibility test'\n        uses: buildsville/add-remove-label@v2.0.1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          labels: compatibility test\n          type: remove\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: \"${{ github.event.pull_request.head.sha }}\"\n      - name: install dependencies\n        uses: ./.github/actions/install-dependencies\n      - name: Get Telepresence Version\n        id: version\n        run: |\n          v=$(go run build-aux/genversion/main.go ${{github.run_id}})\n          echo \"TELEPRESENCE_VERSION=$v\" >> \"$GITHUB_ENV\"\n          echo \"TELEPRESENCE_SEMVER=${v#v}\" >> \"$GITHUB_ENV\"\n      - name: Start minikube\n        if: runner.os == 'Linux'\n        uses: medyagh/setup-minikube@latest\n        with:\n          kubernetes-version: v1.33.5\n      - name: Build client\n        run: make build\n      - name: Build images\n        run: make client-image tel2-image routecontroller-image\n      - name: Load images into minikube\n        run: |\n          minikube image load ${{env.TELEPRESENCE_REGISTRY}}/telepresence:${{env.TELEPRESENCE_SEMVER}}\n          minikube image load ${{env.TELEPRESENCE_REGISTRY}}/tel2:${{env.TELEPRESENCE_SEMVER}}\n          minikube image load ${{env.TELEPRESENCE_REGISTRY}}/route-controller:${{env.TELEPRESENCE_SEMVER}}\n      - name: Run integration tests\n        if: github.event.label.name == 'ok to test'\n        uses: nick-fields/retry/@v3\n        with:\n          max_attempts: 3\n          shell: bash\n          timeout_minutes: 90\n          command: |\n            set -ex\n            if [[ ${RUNNER_OS} == \"Windows\" ]]; then\n              export PATH=\"$PATH:/C/Program Files/SSHFS-Win/bin:$HOME/kubectl-plugins\"\n            fi\n            make check-integration\n      - name: Compatibility with older manager and agent\n        if:  ${{ github.event.label.name == 'compatibility test' }}\n        env:\n          DEV_MANAGER_VERSION: \"2.24.1\"\n          DEV_MANAGER_REGISTRY: ghcr.io/telepresenceio\n        uses: nick-fields/retry/@v3\n        with:\n          max_attempts: 3\n          shell: bash\n          timeout_minutes: 90\n          command: |\n            set -ex\n            if [[ ${RUNNER_OS} == \"Windows\" ]]; then\n              export PATH=\"$PATH:/C/Program Files/SSHFS-Win/bin:$HOME/kubectl-plugins\"\n            fi\n            make check-integration\n      - name: Compatibility with older client\n        if:  ${{ github.event.label.name == 'compatibility test' }}\n        env:\n          DEV_CLIENT_VERSION: \"2.24.1\"\n          DEV_CLIENT_REGISTRY: ghcr.io/telepresenceio\n        uses: nick-fields/retry/@v3\n        with:\n          max_attempts: 3\n          shell: bash\n          timeout_minutes: 90\n          command: |\n            set -ex\n            if [[ ${RUNNER_OS} == \"Windows\" ]]; then\n              export PATH=\"$PATH:/C/Program Files/SSHFS-Win/bin:$HOME/kubectl-plugins\"\n            fi\n            make check-integration\n      - uses: ./.github/actions/upload-logs\n        env:\n          LOG_SUFFIX: \"${{ runner.os }}-${{ runner.arch }}-${{ matrix.clusters.distribution }}-${{ matrix.clusters.version }}\"\n        if: ${{ always() }}\n"
  },
  {
    "path": ".github/workflows/go-dependency-submission.yaml",
    "content": "name: Go Dependency Submission\non:\n  push:\n    branches:\n      - release/v2\n\n# The API requires write permission on the repository to submit dependencies\npermissions:\n  contents: write\n\njobs:\n  go-action-detection:\n    runs-on: ubuntu-latest\n    steps:\n      - name: 'Checkout Repository'\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-go@v5\n        with:\n          go-version: stable\n      # we need fuseftp.bits\n      - name: \"Build dependencies\"\n        run: make build-deps\n\n      - name: Run snapshot action\n        uses: actions/go-dependency-submission@v1\n        with:\n          go-mod-path: go.mod\n          go-build-target: cmd/telepresence/main.go\n"
  },
  {
    "path": ".github/workflows/image-scan.yaml",
    "content": "name: image-scan\non:\n  push:\n    branches:\n      - release/v2\n  pull_request:\njobs:\n  trivy-container-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: ${{ github.event.pull_request.head.sha }}\n      - uses: actions/setup-go@v5\n        with:\n          go-version: stable\n      - name: Build dev image\n        run: |\n          make save-tel2-image\n      - name: Scan\n        uses: aquasecurity/trivy-action@master\n        with:\n          input: build-output/tel2-image.tar\n          format: sarif\n          exit-code: 0 # only warn for now until we have backed it into our processes\n          output: trivy-results.sarif\n          ignore-unfixed: true\n          vuln-type: \"os,library\"\n          severity: \"CRITICAL,HIGH\"\n          hide-progress: false\n      - name: Upload Scan to GitHub Security Tab\n        uses: github/codeql-action/upload-sarif@v3\n        with:\n          sarif_file: \"trivy-results.sarif\"\n  pass:\n    name: image-scan\n    needs:\n      - trivy-container-scan\n    runs-on: ubuntu-latest\n    steps:\n      - name: No-Op\n        if: ${{ false }}\n        run: \"echo Pass\"\n"
  },
  {
    "path": ".github/workflows/licenses.yaml",
    "content": "name: \"License verification\"\non:\n  pull_request:\n  workflow_dispatch:\njobs:\n  dependency_info_linux:\n    name: \"Linux: Verify use of forbidden licenses\"\n    runs-on: ubuntu-latest\n    outputs:\n      is-up-to-date: ${{ steps.check.outputs.up-to-date }}\n      diff: ${{ steps.check.outputs.diff }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: \"${{ github.event.pull_request.head.sha }}\"\n      - name: install dependencies\n        uses: ./.github/actions/install-dependencies\n      - name: \"Generate dependency information\"\n        shell: bash\n        run: make generate\n      - name: \"Update dependency information after dependabot change\"\n        uses: datawire/go-mkopensource/actions/save-dependabot-changes@v0.0.7\n        id: changed-by-dependabot\n        with:\n          branches_to_skip: 'release/v2'\n      - name: \"Abort if dependencies changed\"\n        if: steps.changed-by-dependabot.outputs.is_dirty == 'true'\n        run: |\n          echo \"Dependabot triggered a dependency update. Aborting workflow.\"\n          exit 1\n      - name: \"Check dependency files are up-to-date\"\n        id: check\n        shell: bash\n        run: |\n          set -e\n          git add .\n          if [[ -n \"$(git status --porcelain)\" ]]; then\n            echo \"diff=$(PAGER= git diff --cached 2>&1 | base64 -w0)\" >> $GITHUB_OUTPUT\n            echo \"up-to-date=no\" >> $GITHUB_OUTPUT\n          else\n            echo \"up-to-date=yes\" >> $GITHUB_OUTPUT\n          fi\n  up_to_date_check_linux:\n    name: \"Linux: Check out-of-date licenses\"\n    needs: dependency_info_linux\n    runs-on: ubuntu-latest\n    steps:\n      - name: \"Check dependency files are up-to-date\"\n        shell: bash\n        run: |\n          set -e\n          if [[ \"${{needs.dependency_info_linux.outputs.is-up-to-date}}\" == \"no\" ]]; then\n            echo '::error:: Changes detected after dependency generation. Run ' \\\n              '`make generate` and commit the latest version of the ' \\\n              'dependency information files'\n\n            echo ${{needs.dependency_info_linux.outputs.diff}} | base64 -d\n            exit 1\n          fi\n\n          echo '::info:: Files are up-to-date'\n"
  },
  {
    "path": ".github/workflows/release-teleroute.yaml",
    "content": "on:\n  push:\n    tags:\n      # These aren't regexps. They are \"Workflow Filter patterns\"\n      - teleroute-[0-9]+.[0-9]+.[0-9]\n\njobs:\n  build-release:\n    strategy:\n      fail-fast: false\n      matrix:\n        architecture:\n          - amd64\n          - arm64\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-go@v5\n        with:\n          go-version: stable\n      - name: Push image\n        working-directory: ./cmd/teleroute\n        env:\n          PLUGIN_ARCH: ${{ matrix.architecture }}\n          VER: ${{ github.ref }}\n        run: |\n          echo '${{ secrets.GITHUB_TOKEN }}' | docker login ghcr.io -u='${{ github.actor }}' --password-stdin\n          PLUGIN_VERSION=${VER#refs/tags/teleroute-} make push\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Releases\n\non:\n  push:\n    tags:\n      # These aren't regexps. They are \"Workflow Filter patterns\"\n      - v[0-9]+.[0-9]+.[0-9]\n      - v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+\n      - v[0-9]+.[0-9]+.[0-9]+-test.[0-9]+\n\nenv:\n  TELEPRESENCE_REGISTRY: ghcr.io/telepresenceio\n\njobs:\n  build-release:\n    strategy:\n      fail-fast: false\n      matrix:\n        runner:\n          - ubuntu-latest\n          - macos-latest\n          - windows-latest\n        arch:\n          - amd64\n          - arm64\n    runs-on: ${{ matrix.runner }}\n    env:\n      GOARCH: ${{ matrix.arch }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: ./.github/actions/install-dependencies\n        name: install dependencies\n      - name: set version\n        shell: bash\n        run: echo \"TELEPRESENCE_VERSION=${{ github.ref_name }}\" >> $GITHUB_ENV\n      - name: generate binaries\n        run: make release-binary\n      - name: Install WiX Toolset\n        if: runner.os == 'Windows' && matrix.arch == 'amd64'\n        shell: pwsh\n        run: |\n          dotnet tool install --global wix --version 6.0.2\n          wix extension add -g WixToolset.BootstrapperApplications.wixext/6.0.2\n          wix extension add -g WixToolset.UI.wixext/6.0.2\n          wix extension add -g WixToolset.Util.wixext/6.0.2\n      - name: Build WiX Installer\n        if: runner.os == 'Windows' && matrix.arch == 'amd64'\n        shell: bash\n        run: |\n          export PATH=\"$USERPROFILE/.dotnet/tools:$PATH\"\n          cd build-aux/wix-installer\n          make bundle ARCH=${{ matrix.arch }}\n          cp ../../build-output/bin/TelepresenceInstall.exe ../../build-output/release/telepresence-windows-${{ matrix.arch }}-setup.exe\n      - name: Install nfpm\n        if: runner.os == 'Linux'\n        shell: bash\n        run: |\n          # Always download x86_64 nfpm since ubuntu-latest runners are x86_64\n          # nfpm cross-compiles packages for the target arch specified in config\n          curl -sfL \"https://github.com/goreleaser/nfpm/releases/download/v2.44.1/nfpm_2.44.1_Linux_x86_64.tar.gz\" | tar xz -C /tmp\n          sudo mv /tmp/nfpm /usr/local/bin/nfpm\n      - name: Build Linux Packages\n        if: runner.os == 'Linux'\n        shell: bash\n        run: |\n          cd build-aux/systemd-installer\n          VERSION=\"${TELEPRESENCE_VERSION#v}\" ARCH=${{ matrix.arch }} ./build-packages.sh\n      - name: Upload binaries\n        uses: actions/upload-artifact@v4\n        with:\n          name: binaries-${{ matrix.runner }}-${{ matrix.arch }}\n          path: build-output/release\n          retention-days: 1\n\n  push-images:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: ./.github/actions/install-dependencies\n        name: install dependencies\n      - name: set version\n        shell: bash\n        run: |\n          v=${{ github.ref_name }}\n          echo \"TELEPRESENCE_VERSION=$v\" >> \"$GITHUB_ENV\"\n          echo \"TELEPRESENCE_SEMVER=${v#v}\" >> \"$GITHUB_ENV\"\n      - name: Setup docker buildx\n        uses: docker/setup-buildx-action@v3\n        with:\n          platforms: linux/amd64,linux/arm64\n      - name: Build image dependencies\n        run: make images-deps\n      - name: Make helm chart\n        run: make helm-chart\n      - name: Log in to registry\n        run: echo \"${{ secrets.GITHUB_TOKEN }}\" | docker login ghcr.io -u ${{ github.actor }} --password-stdin\n      - name: Push client image\n        run: |\n          docker buildx build --platform=linux/amd64,linux/arm64 --build-arg TELEPRESENCE_VERSION=${{env.TELEPRESENCE_SEMVER}} \\\n          --push --tag ${{env.TELEPRESENCE_REGISTRY}}/telepresence:${{env.TELEPRESENCE_SEMVER}} -f build-aux/docker/images/Dockerfile.client .\n      - name: Push tel2 image\n        run: |\n          docker buildx build --platform=linux/amd64,linux/arm64 --build-arg TELEPRESENCE_VERSION=${{env.TELEPRESENCE_SEMVER}} \\\n          --push --tag ${{env.TELEPRESENCE_REGISTRY}}/tel2:${{env.TELEPRESENCE_SEMVER}} -f build-aux/docker/images/Dockerfile.traffic .\n      - name: Push routeController image\n        run: |\n          docker buildx build --platform=linux/amd64,linux/arm64 \\\n          --push --tag ${{env.TELEPRESENCE_REGISTRY}}/route-controller:${{env.TELEPRESENCE_SEMVER}} -f build-aux/docker/images/Dockerfile.routecontroller .\n      - name: Push Helm Chart\n        run: helm push build-output/telepresence-oss-chart.tgz oci://${{env.TELEPRESENCE_REGISTRY}}\n      - name: Log out from registry\n        if: always()\n        run: docker logout\n\n  publish-release:\n    runs-on: ubuntu-latest\n    needs:\n      - build-release\n    outputs:\n      draft: ${{ steps.semver_check.outputs.draft }}\n      prerelease: ${{ steps.semver_check.outputs.prerelease }}\n      semver: ${{ steps.semver_check.outputs.semver }}\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n      - name: Display structure of downloaded files\n        run: ls -R\n      - name: Determine if version is RC, TEST, or GA\n        id: semver_check\n        run: |\n          v=${{ github.ref_name }}\n          echo \"semver=${v#v}\" >> \"$GITHUB_OUTPUT\"\n          if [[ \"${v}\" =~ ^v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+$ ]]; then\n            echo \"make_latest=false\" >> $GITHUB_OUTPUT\n            echo \"draft=false\" >> $GITHUB_OUTPUT\n            echo \"prerelease=true\" >> $GITHUB_OUTPUT\n          elif [[ \"${v}\" =~ ^v[0-9]+.[0-9]+.[0-9]+-test.[0-9]+$ ]]; then\n            echo \"make_latest=false\" >> $GITHUB_OUTPUT\n            echo \"draft=false\" >> $GITHUB_OUTPUT\n            echo \"prerelease=true\" >> $GITHUB_OUTPUT\n          elif [[ \"${v}\" =~ ^v[0-9]+.[0-9]+.[0-9]+-draft.[0-9]+$ ]]; then\n            echo \"make_latest=false\" >> $GITHUB_OUTPUT\n            echo \"draft=true\" >> $GITHUB_OUTPUT\n            echo \"prerelease=false\" >> $GITHUB_OUTPUT\n          else\n            echo \"make_latest=true\" >> $GITHUB_OUTPUT\n            echo \"draft=false\" >> $GITHUB_OUTPUT\n            echo \"prerelease=false\" >> $GITHUB_OUTPUT\n          fi\n      - name: Create draft release\n        if: ${{ steps.semver_check.outputs.draft == 'true' }}\n        uses: ncipollo/release-action@v1\n        with:\n          artifacts: \"binaries-*/*\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          draft:\n          tag: ${{ github.ref_name }}\n          body: |\n            ## Draft Release\n            For more information, visit our [installation docs](https://www.telepresence.io/docs/latest/quick-start).\n      - name: Create release\n        if: ${{ steps.semver_check.outputs.draft == 'false' }}\n        uses: ncipollo/release-action@v1\n        with:\n          artifacts: \"binaries-*/*\"\n          token: ${{ secrets.GITHUB_TOKEN }}\n          prerelease: ${{ steps.semver_check.outputs.prerelease }}\n          makeLatest: ${{ steps.semver_check.outputs.make_latest }}\n          tag: ${{ github.ref_name }}\n          body: |\n            ## Official Release Artifacts\n\n            ### Installers (with root daemon as a system service)\n            These installers include the option to run the root daemon as a system service, eliminating the need for elevated privileges when using Telepresence.\n\n            | Platform | Architecture | Package |\n            |----------|--------------|---------|\n            | Linux (Debian/Ubuntu) | amd64 | [telepresence-linux-amd64.deb](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-${{ steps.semver_check.outputs.semver }}-linux-amd64.deb) |\n            | Linux (Debian/Ubuntu) | arm64 | [telepresence-linux-arm64.deb](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-${{ steps.semver_check.outputs.semver }}-linux-arm64.deb) |\n            | Linux (Fedora/RHEL) | amd64 | [telepresence-linux-amd64.rpm](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-${{ steps.semver_check.outputs.semver }}-linux-amd64.rpm) |\n            | Linux (Fedora/RHEL) | arm64 | [telepresence-linux-arm64.rpm](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-${{ steps.semver_check.outputs.semver }}-linux-arm64.rpm) |\n            | macOS | amd64 | [telepresence-darwin-amd64.pkg](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-darwin-amd64.pkg) |\n            | macOS | arm64 | [telepresence-darwin-arm64.pkg](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-darwin-arm64.pkg) |\n            | Windows | amd64 | [telepresence-windows-amd64-setup.exe](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-windows-amd64-setup.exe) |\n            | Windows | arm64 | *Not available — WinFSP and SSHFS-Win installers lack arm64 versions. Use the zip file below.* |\n\n            ### Standalone Binaries\n            Standalone binaries for manual installation. The root daemon runs on-demand with elevated privileges.\n\n            | Platform | Architecture | Binary |\n            |----------|--------------|--------|\n            | Linux | amd64 | [telepresence-linux-amd64](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-linux-amd64) |\n            | Linux | arm64 | [telepresence-linux-arm64](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-linux-arm64) |\n            | macOS | amd64 | [telepresence-darwin-amd64](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-darwin-amd64) |\n            | macOS | arm64 | [telepresence-darwin-arm64](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-darwin-arm64) |\n            | Windows | amd64 | [telepresence-windows-amd64.zip](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-windows-amd64.zip) |\n            | Windows | arm64 | [telepresence-windows-arm64.zip](https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}/telepresence-windows-arm64.zip) |\n\n            ### Helm Chart\n               - 📦 [Telepresence Helm Chart](https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss/${{ steps.semver_check.outputs.semver }})\n\n            For more information, visit our [installation docs](https://www.telepresence.io/docs/quick-start).\n      - uses: actions/checkout@v4\n        if: ${{ steps.semver_check.outputs.make_latest == 'true' }}\n      - name: Update Homebrew\n        if: ${{ steps.semver_check.outputs.make_latest == 'true' }}\n        run: |\n          v=${{ github.ref_name }}\n          packaging/homebrew-package.sh \"${v#v}\" \"${{ vars.GH_BOT_USER }}\" \"${{ vars.GH_BOT_EMAIL }}\" \"${{ secrets.HOMEBREW_TAP_TOKEN }}\"\n\n  prune-images:\n    if: ${{ always() }}\n    runs-on: ubuntu-latest\n    permissions:\n      packages: write\n    needs:\n      - push-images\n    steps:\n      - name: Prune tel2 and telepresence\n        uses: dataaxiom/ghcr-cleanup-action@v1\n        with:\n          owner: telepresenceio\n          packages: tel2,telepresence\n          token: ${{ secrets.GITHUB_TOKEN }}\n          delete-untagged: true\n          delete-ghost-images: true\n          delete-partial-images: true\n          delete-orphaned-images: true\n\n  build-macos-pkg:\n    # This job builds signed and notarized macOS .pkg installers.\n    # It uses a protected environment requiring approval from authorized reviewers.\n    # If not approved, the release proceeds without .pkg installers (standalone binaries are still available).\n    environment: macos-signing\n    needs:\n      - publish-release\n    strategy:\n      fail-fast: false\n      matrix:\n        arch:\n          - amd64\n          - arm64\n    runs-on: macos-latest\n    env:\n      GOARCH: ${{ matrix.arch }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: ./.github/actions/install-dependencies\n        name: install dependencies\n      - name: set version\n        shell: bash\n        run: echo \"TELEPRESENCE_VERSION=${{ github.ref_name }}\" >> $GITHUB_ENV\n      - name: generate binaries\n        run: make release-binary\n      - name: Import Apple certificates\n        env:\n          MACOS_CERTIFICATE_P12: ${{ secrets.MACOS_CERTIFICATE_P12 }}\n          MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}\n        run: |\n          # Create a temporary keychain\n          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db\n          KEYCHAIN_PASSWORD=$(openssl rand -base64 32)\n\n          security create-keychain -p \"$KEYCHAIN_PASSWORD\" \"$KEYCHAIN_PATH\"\n          security set-keychain-settings -lut 21600 \"$KEYCHAIN_PATH\"\n          security unlock-keychain -p \"$KEYCHAIN_PASSWORD\" \"$KEYCHAIN_PATH\"\n\n          # Import certificates from base64-encoded P12\n          echo \"$MACOS_CERTIFICATE_P12\" | base64 --decode > $RUNNER_TEMP/certificate.p12\n          security import $RUNNER_TEMP/certificate.p12 -P \"$MACOS_CERTIFICATE_PASSWORD\" -A -t cert -f pkcs12 -k \"$KEYCHAIN_PATH\"\n          rm $RUNNER_TEMP/certificate.p12\n\n          # Add keychain to search list and set as default\n          security list-keychain -d user -s \"$KEYCHAIN_PATH\" login.keychain\n          security default-keychain -s \"$KEYCHAIN_PATH\"\n\n          # Allow codesign to access the keychain without prompting\n          security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k \"$KEYCHAIN_PASSWORD\" \"$KEYCHAIN_PATH\"\n\n          # Store keychain path for cleanup\n          echo \"KEYCHAIN_PATH=$KEYCHAIN_PATH\" >> $GITHUB_ENV\n\n      - name: Build signed macOS Installer\n        env:\n          MACOS_SIGN_APPLICATION: ${{ secrets.MACOS_SIGN_APPLICATION }}\n          MACOS_SIGN_INSTALLER: ${{ secrets.MACOS_SIGN_INSTALLER }}\n          MACOS_NOTARIZE_APPLE_ID: ${{ secrets.MACOS_NOTARIZE_APPLE_ID }}\n          MACOS_NOTARIZE_TEAM_ID: ${{ secrets.MACOS_NOTARIZE_TEAM_ID }}\n          MACOS_NOTARIZE_PASSWORD: ${{ secrets.MACOS_NOTARIZE_PASSWORD }}\n        run: |\n          cd build-aux/pkg-installer\n          VERSION=\"${TELEPRESENCE_VERSION#v}\" ARCH=${{ matrix.arch }} ./build-pkg.sh\n\n      - name: Cleanup macOS keychain\n        if: always() && env.KEYCHAIN_PATH != ''\n        run: |\n          security delete-keychain \"$KEYCHAIN_PATH\" || true\n\n      - name: Add signed package to release\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          cp build-output/Telepresence.pkg telepresence-darwin-${{ matrix.arch }}.pkg\n          gh release upload ${{ github.ref_name }} telepresence-darwin-${{ matrix.arch }}.pkg --clobber\n\n  test-release:\n    needs:\n      - push-images\n      - publish-release\n    if: ${{ needs.publish-release.outputs.draft == 'false' }}\n    strategy:\n      fail-fast: false\n      matrix:\n        runner:\n          - ubuntu-latest\n          - macos-latest\n          - windows-latest\n        arch:\n          - amd64\n          - arm64\n    runs-on: ${{ matrix.runner }}\n    steps:\n      - name: download binary\n        env:\n          DOWNLOAD_URL: \"https://github.com/telepresenceio/telepresence/releases/download/${{ github.ref_name }}\"\n        shell: bash\n        run: |\n          if [ \"${{ runner.os }}\" = \"macOS\" ]; then\n            curl -fL ${{ env.DOWNLOAD_URL }}/telepresence-darwin-${{ matrix.arch }} -o ./telepresence || { echo \"Curl command failed\" ; exit 1; }\n          elif [ \"${{ runner.os }}\" = \"Windows\" ]; then\n            curl -fL ${{ env.DOWNLOAD_URL }}/telepresence-windows-${{ matrix.arch }}.zip -o ./telepresence.zip || { echo \"Curl command failed\" ; exit 1; }\n            unzip ./telepresence.zip || { echo \"Unzip command failed\" ; exit 1; }\n          else\n              curl -fL ${{ env.DOWNLOAD_URL }}/telepresence-linux-${{ matrix.arch }} -o ./telepresence || { echo \"Curl command failed\" ; exit 1; }\n          fi\n      - name: test binary\n        shell: bash\n        if: ${{ !((runner.os == 'Linux' || runner.os == 'Windows') && runner.arch == 'X64' && matrix.arch == 'arm64') }}\n        run: |\n          chmod +x ./telepresence\n\n          output=$(./telepresence version)\n\n          if [ $? -eq 0 ]; then\n              echo \"Telepresence command executed successfully\"\n          else\n              echo \"Telepresence command failed\"\n              exit 1\n          fi\n\n          echo \"$output\" | grep -q \"Client\\s*:\\s*${{ github.ref_name }}\"\n\n          if [ $? -eq 0 ]; then\n              echo \"Version match!\"\n          else\n              echo \"Version does not match!\"\n              exit 1\n          fi"
  },
  {
    "path": ".github/workflows/stale.yaml",
    "content": "name: 'Close stale issues and PR'\non:\n  schedule:\n    - cron: '12 10 * * *'\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v9\n        with:\n          stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment, or this will be closed in 7 days.'\n          stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment, or this will be closed in 14 days.'\n          close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.'\n          close-pr-message: 'This PR was closed because it has been stalled for 14 days with no activity.'\n          days-before-issue-stale: 60\n          days-before-pr-stale: 30\n          days-before-issue-close: 7\n          days-before-pr-close: 14\n          operations-per-run: 80\n          exempt-issue-labels: feature,friction\n"
  },
  {
    "path": ".github/workflows/unit_tests.yaml",
    "content": "name: \"Build and Unit test\"\non:\n  pull_request:\nenv:\n  HOMEBREW_NO_INSTALL_FROM_API:\njobs:\n  unit:\n    strategy:\n      fail-fast: false\n      matrix:\n        runners:\n          - ubuntu-latest\n          - macos-latest\n          - windows-latest\n    runs-on: ${{ matrix.runners }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          ref: \"${{ github.event.pull_request.head.sha }}\"\n      - name: install dependencies\n        uses: ./.github/actions/install-dependencies\n      - name: install build dependencies\n        run: make build-deps\n      - name: Lint\n        if: ${{ runner.os != 'Windows' }}\n        uses: golangci/golangci-lint-action@v7\n        with:\n          args: --timeout 8m ./...\n      - name: Lint (limited on windows)\n        if: ${{ runner.os == 'Windows' }}\n        uses: golangci/golangci-lint-action@v7\n        with:\n          args: --timeout 8m ./cmd/telepresence/... ./integration_test/... ./pkg/...\n      - name: Build\n        run: make build\n      - name: Run tests\n        uses: nick-fields/retry/@v3\n        with:\n          max_attempts: 3\n          timeout_minutes: 12\n          command: make check-unit\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories\nvendor/\n\n# Editor nonsense\n.vscode\n.idea\n*.iml\n*.swp\n.DS_Store\n\nbuild-output\n/tools/*\n!/tools/src/\n\n# Don't accidentally add files from `go build`\ntelepresence\ntraffic\ntst-manager\n!/cmd/*/\nmcp-tools.json\n\n# Include Chart directory\n!/charts/telepresence-oss\n# But don't include this one\n/charts/telepresence-oss/k8s-defs.json\n\n# Downloaded fuseftp bits\nfuseftp.bits\n\n# Needed to build on windows\n.wintools\n.gocache\n\n# Don't accidentally add binaries built byintegration tests\n/integration_test/testdata/echo-server/echo-server\n/build-aux/genversion/genversion\n\n# Vagrant\n.vagrant/\ntests.log\n/.claude/settings.local.json\n/bugs/\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nrun:\n  build-tags:\n    - citest\n  modules-download-mode: readonly\nlinters:\n  enable:\n    - asasalint\n    - asciicheck\n    - bidichk\n    - bodyclose\n    - copyloopvar\n    - cyclop\n    - decorder\n    - depguard\n    - dogsled\n    - durationcheck\n    - errname\n    - forbidigo\n    - gochecknoglobals\n    - gocognit\n    - gocritic\n    - gocyclo\n    - godot\n    - goheader\n    - gomodguard\n    - goprintffuncname\n    - grouper\n    - importas\n    - lll\n    - loggercheck\n    - makezero\n    - misspell\n    - nakedret\n    - nolintlint\n    - nosprintfhostport\n    - prealloc\n    - predeclared\n    - reassign\n    - unconvert\n    - unparam\n    - usestdlibvars\n    - whitespace\n  disable:\n    - containedctx\n    - contextcheck\n    - dupl\n    - err113\n    - errchkjson\n    - errorlint\n    - exhaustive\n    - exhaustruct\n    - forcetypeassert\n    - funlen\n    - gochecknoinits\n    - goconst\n    - godox\n    - gomoddirectives\n    - gosec\n    - interfacebloat\n    - ireturn\n    - maintidx\n    - nestif\n    - nilerr\n    - nilnil\n    - nlreturn\n    - noctx\n    - nonamedreturns\n    - paralleltest\n    - promlinter\n    - revive\n    - rowserrcheck\n    - sqlclosecheck\n    - tagliatelle\n    - testpackage\n    - thelper\n    - tparallel\n    - varnamelen\n    - wastedassign\n    - wrapcheck\n    - wsl\n  settings:\n    cyclop:\n      max-complexity: 32\n    depguard:\n      rules:\n        main:\n          files:\n            - $all\n          deny:\n            - pkg: io/ioutil\n              desc: '`io/ioutil` is deprecated in Go 1.16, use `io` or `os` instead'\n            - pkg: syscall\n              desc: Use `golang.org/x/sys/...` instead of `syscall`\n            - pkg: github.com/golang/protobuf\n              desc: Use `google.golang.org/protobuf` instead of `github.com/golang/protobuf`\n            - pkg: github.com/kballard/go-shellquote\n              desc: Use `github.com/telepresenceio/telepresence/pkg/shellquote.ShellString` instead of `github.com/kballard/go-shellquote.Join`\n    forbidigo:\n      forbid:\n        - pattern: '^os\\.IsNotExist$'\n          msg: Use errors.Is(err, fs.ErrNotExist)\n    gocognit:\n      min-complexity: 60\n    gocyclo:\n      min-complexity: 35\n    gomodguard:\n      blocked:\n        modules:\n          - gotest.tools:\n              recommendations:\n                - github.com/stretchr/testify\n                - github.com/google/go-cmp/cmp\n          - gotest.tools/v2:\n              recommendations:\n                - github.com/stretchr/testify\n                - github.com/google/go-cmp/cmp\n          - gotest.tools/v3:\n              recommendations:\n                - github.com/stretchr/testify\n                - github.com/google/go-cmp/cmp\n    lll:\n      line-length: 180\n      tab-width: 2\n    nolintlint:\n      require-explanation: true\n      require-specific: true\n      allow-no-explanation:\n        - gocognit\n      allow-unused: true\n    prealloc:\n      range-loops: false\n    staticcheck:\n      checks:\n        - all\n        # Don't check \"Apply De Morgan's law\".\n        # https://staticcheck.dev/docs/checks/#QF1001\n        - -QF1001\n        # Don't check \"Incorrect or missing package comment\".\n        # https://staticcheck.dev/docs/checks/#ST1000\n        - -ST1000\n        # Don't check \"Poorly chosen identifier\".\n        # https://staticcheck.dev/docs/checks/#ST1003\n        - -ST1003\n\n\n  exclusions:\n    generated: lax\n    rules:\n      - linters:\n          - const\n          - dupl\n          - gochecknoglobals\n          - goconst\n          - golint\n          - lll\n          - unparam\n        path: _test\\.go\n      - path: (.+)\\.go$\n        text: Error return value of `(\\w+\\.)+(Close|CloseSend|Flush|Remove|(Un)?Setenv|(Fp|P)rint(f|ln))\\` is not checked\n      - path: (.+)\\.go$\n        text: 'structtag: struct field \\w+ repeats json tag'\n      - path: (.+)\\.go$\n        text: Subprocess launched with function call as argument or cmd arguments\n      - linters:\n          - cyclop\n        path: (.+)_test\\.go\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\nissues:\n  max-same-issues: 0\nformatters:\n  enable:\n    - gofmt\n    - gofumpt\n  exclusions:\n    generated: lax\n    paths:\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": ".mailmap",
    "content": "Alex Gervais <alex@datawire.io>                        alex <alex@datawire.io>\nAlex Gervais <alex@datawire.io>                        <alex.gervais@gmail.com>\nDonny Yung <donaldyung@datawire.io>                    <donaldryung@gmail.com>\nFlynn <flynn@datawire.io>                              <kflynn@users.noreply.github.com>\nJohn Esmet <johnesmet@datawire.io>                     <jesmet@appnexus.com>\nLuke Shumaker <lukeshu@datawire.io>                    <lukeshu@lukeshu.com>\nMaxime Legault-Venne <maximelegaultvenne@datawire.io>  <mlegoven@hotmail.com>\nRafael Schloming <rhs@datawire.io>                     <rhs@alum.mit.edu>\nThomas Hallgren <thomas@datawire.io>                   <thomas@tada.se>\n\nAbhay Saxena <ark3@email.com>            <ark3@datawire.io>\nAlvaro Saurin <alvaro.saurin@gmail.com>  <1841612+inercia@users.noreply.github.com>\nPhilip Lombardi <plombardi89@gmail.com>  plombardi <plombardi89@gmail.com>\nPhilip Lombardi <plombardi89@gmail.com>  <893096+plombardi89@users.noreply.github.com>\nPhilip Lombardi <plombardi89@gmail.com>  <plombardi@datawire.io>\n"
  },
  {
    "path": ".protolint.yaml",
    "content": "lint:\n  # Which rules to enable/disable.\n  rules:\n    remove:\n      - ENUM_FIELD_NAMES_PREFIX\n  # Settings for those rules.\n  rules_option:\n    max_line_length:\n      max_chars: 120\n"
  },
  {
    "path": "CHANGELOG.OLD.md",
    "content": "# Changelog\n\n### 2.13.3 (May 25, 2023)\n\n- Feature: Add `.Values.hooks.curl.imagePullSecrets` and `.Values.hooks.curl.imagePullSecrets` to Helm values.\n  PR [3079](https://github.com/telepresenceio/telepresence/pull/3079).\n\n- Change: The default setting of the reinvocationPolicy for the mutating webhook dealing with agent injections\n  changed from `Never` to `IfNeeded`.\n\n- Bugfix: The `eks.amazonaws.com/serviceaccount` volume injected by EKS is now exported and remotely mounted\n  during an intercept.\n  Ticket [3166](https://github.com/telepresenceio/telepresence/issues/3166).\n\n- Bugfix: The mutating webhook now correctly applies the namespace selector even if the cluster version contains\n  non-numeric characters. For example, it can now handle versions such as Major:\"1\", Minor:\"22+\".\n  PR [3184](https://github.com/telepresenceio/telepresence/pull/3184).\n\n- Bugfix: The \"telepresence\" Docker network will now propagate DNS AAAA queries to the Telepresence DNS resolver when\n  it runs in a Docker container.\n  Ticket [3179](https://github.com/telepresenceio/telepresence/issues/3179).\n\n- Bugfix: Running `telepresence intercept --local-only --docker-run` no longer  results in a panic.\n  Ticket [3171](https://github.com/telepresenceio/telepresence/issues/3171).\n\n- Bugfix: Running `telepresence intercept --local-only --mount false` no longer results in an incorrect error message\n  saying \"a local-only intercept cannot have mounts\".\n  Ticket [3171](https://github.com/telepresenceio/telepresence/issues/3171).\n\n- Bugfix: The helm chart now correctly handles custom `agentInjector.webhook.port` that was not being set in hook URLs.\n  PR [3161](https://github.com/telepresenceio/telepresence/pull/3161).\n\n- Bugfix: `.intercept.disableGlobal` and `.timeouts.agentArrival` are now correctly honored.\n\n### 2.13.2 (May 12, 2023)\n- Bugfix: Replaced `/` characters with a `-` when the authenticator service creates the kubeconfig in the Telepresence cache.\n  PR [3167](https://github.com/telepresenceio/telepresence/pull/3167).\n\n- Feature: Configurable strategy (`auto`, `powershell`. or `registry`) to set the global DNS search path on Windows. Default\n  is `auto` which means try `powershell` first, and if it fails, fall back to `registry`.\n  Ticket [3152](https://github.com/telepresenceio/telepresence/issues/3152).\n\n- Feature: The timeout for the traffic manager to wait for traffic agent to arrive can\n  now be configured in the `values.yaml` file using `timeouts.agentArrival`. The default\n  timeout is still 30 seconds.\n  PR [3148](https://github.com/telepresenceio/telepresence/pull/3148).\n\n- Bugfix: The automatic discovery of a local container based cluster (minikube or kind) used when the\n  Telepresence daemon runs in a container, now works on macOS and Windows, and with different profiles,\n  ports, and cluster names\n  PR [3165](https://github.com/telepresenceio/telepresence/pull/3165).\n\n- Bugfix: FTP Stability improvements. Multiple simultaneous intercepts can transfer large files in bidirectionally and in parallel.\n  PR [3157](https://github.com/telepresenceio/telepresence/pull/3157).\n\n- Bugfix: Pods using persistent volumes no longer causes timeouts when intercepted.\n\n- Bugfix: Ensure that `telepresence connect` succeeds even though DNS isn't configured correctly.\n  Ticket [3143](https://github.com/telepresenceio/telepresence/issues/3143).\n  PR [3154](https://github.com/telepresenceio/telepresence/pull/3154).\n\n- Bugfix: The traffic-manager would sometimes panic with a \"close of closed channel\" message and exit.\n  PR [3160](https://github.com/telepresenceio/telepresence/pull/3160).\n\n- Bugfix: The traffic-manager would sometimes panic and exit after some time due to a type cast panic.\n  Ticket [3149](https://github.com/telepresenceio/telepresence/issues/3149).\n\n### 2.13.1 (April 20, 2023)\n\n- Change: Update ambassador-agent to version 1.13.13\n\n### 2.13.0 (April 18, 2023)\n\n- Feature: The Docker network used by a Kind or Minikube (using the \"docker\" driver) installation, is automatically\n  detected and connected to a Docker container running the Telepresence daemon.\n\n- Feature: Mapped namespaces are included in the output of the `telepresence status` command.\n\n- Feature: There's a new --address flag to the intercept command allowing users to set the target IP of the intercept.\n\n- Feature: The new flags `--docker-build`, and `--docker-build-opt` was added to `telepresence intercept` to facilitate a\n  docker run directly from a docker context.\n\n- Bugfix: Using `telepresence intercept --docker-run` now works with a container based daemon started with `telepresence connect --docker`\n\n- Bugfix: DNS works properly even when no cluster subnet is routed by the Telepresence VIF.\n\n- Bugfix: The Traffic Manager uses a fail-proof way to determine the cluster domain.\n\n- Bugfix: DNS on windows is more reliable and performant.\n\n- Bugfix: The agent is now correctly injected even with a high number of deployment starting at the same time.\n\n- Bugfix: The kubeconfig is made self-contained before running Telepresence daemon in a Docker container.\n\n- Bugfix: The client will no longer need cluster wide permissions when connected to a namespace scoped Traffic Manager.\n\n- BugFix: The version command won't throw an error anymore if there is no kubeconfig file defined.\n\n### 2.12.2 (April 4, 2023)\n\n- Security: Update golang to 1.20.3 to address CVE-2023-24534, CVE-2023-24536, CVE-2023-24537, CVE-2023-24538\n\n### 2.12.1 (March 22, 2023)\n\n- Bugfix: Illegal characters are now replaced when a docker container name is generated from a kubernetes context name.\n\n### 2.12.0 (March 20, 2023)\n\n- Feature: Telepresence can now start or connect to a daemon in a docker container by use of the global `--docker` flag.\n\n- Feature: Adds an authenticator package to support integration with the [client-go credential](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#client-go-credential-plugins) plugins when the\n  daemon runs in a docker container.\n\n- Feature: The `telepresence helm` command now accepts a `--namespace` flag.\n\n- Change: Telepresence will now detect if services and pods are routable, independently of one another, before adding their routes. This is a change from before, when being already able to connect to pods prevented the addition of routes for services too.\n\n- Bugfix: The traffic-manager will no longer panic when the CNAME of kubernetes.default doesn't contain .svc.\n\n- Bugfix: The `telepresence helm install/upgrade --set` family of flags now work correctly with comma separated values.\n\n### 2.11.1 (February 27, 2023)\n\n- Bugfix: The multi-arch build now for the proprietary traffic-manager and traffic-agent now works for both amd64 and arm64.\n\n### 2.11.0 (February 22, 2023)\n\n- Feature: When Telepresence detects that it runs in a docker container, it will now expose its DNS on `localhost:53`. This makes the\n  container itself a DNS server. Very handy when other containers use `--network container:[tp-container]`.\n\n- Feature: A new flag, `--local-mount-port <port>` will make `telepresence intercept --detailed-output --output=[yaml|json]` create\n  a bridge to the remote SFTP service instead of starting an sshfs client. This enables the sshfs client to be started outside of the\n  container, and thus, mount filesystems that then can be used as source for volumes that other containers will use.\n\n- Feature: The Telepresence daemon can now run as a long-lived process in a docker container so that CLI commands that\n  run in other containers can use a common daemon for network access and intercepts.\n\n- Feature: A new boolean flag `--detailed-output` was added to the `telepresence intercept` command. It will output very\n  detailed information about an intercept when used together with `--output=[json|yaml]`.\n  Pull Request [3013](https://github.com/telepresenceio/telepresence/pull/3013).\n\n- Feature: IPv6 support. Thanks to [@0x6a77](https://www.github.com/0x6a77).\n  Ticket [2978](https://github.com/telepresenceio/telepresence/issues/2978).\n\n- Feature: Adds two parameters `--also-proxy` and `--never-proxy` to the `telepresence connect` command.\n  Ticket [2950](https://github.com/telepresenceio/telepresence/issues/2950).\n\n- Feature: Add a parameter `--manager-namespace` to the `telepresence connect` command.\n  Ticket [2968](https://github.com/telepresenceio/telepresence/issues/2968)\n\n- Feature: Add a configuration `cluster.defaultManagerNamespace` for setting the default manager namespace.\n  Ticket [2968](https://github.com/telepresenceio/telepresence/issues/2968)\n\n- Change: The namespace of the connected manager is now displayed in the `telepresence status` output.\n  Ticket [2968](https://github.com/telepresenceio/telepresence/issues/2968)\n\n- Change: Depreciate `--watch` flag in `telepresence list` command. This is now covered by `--output json-stream`\n\n- Change: Add `--output` option `json-stream`\n\n- Bugfix: Fixed a bug when detecting VPN conflicts on macOS that removed conflicting gateway links.\n\n- Bugfix: Fixed a bug where connecting to certain VPNs that map the CIDR range of the cluster would result in no routes getting added.\n  Ticket [3006](https://github.com/telepresenceio/telepresence/issues/3006)\n\n- Bugfix: Support ARM64 architecture\n  Ticket [2786](https://github.com/telepresenceio/telepresence/issues/2786)\n\n### 2.10.6 (February 14, 2023)\n\nSecurity release to rebuild with go 1.19.6\n\n### 2.10.5 (February 6, 2023)\n\n- Change: mTLS Secrets will now be mounted into the traffic agent, instead of expected to be read by it from the API.\n\n- Bugfix: Fixed a bug that prevented the local daemons from automatically reconnecting to the traffic manager when the network connection was lost.\n\n### 2.10.4 (January 20, 2023)\n\n- Bugfix: Fix backward compatibility issue when using traffic-managers of version 2.9.5 or older.\n\n### 2.10.2 (January 16, 2023)\n\n- Bugfix: Ensure that CLI and user-daemon binaries are the same version when running `telepresence helm install`\n  or `telepresence helm upgrade`.\n\n### 2.10.1 (January 11, 2023)\n\n- Bugfix: Fixed a regex in our release process.\n\n### 2.10.0 (January 11, 2023)\n\n- Feature: The Traffic Manager can now be set to either \"team\" mode or \"single user\" mode.\n  When in team mode, intercepts will default to http intercepts.\n\n- Feature: The `telepresence helm` sub-commands `insert` and `upgrade` now accepts all types\n  of helm `--set-XXX` flags.\n\n- Feature: A new `telepresence helm upgrade` command was added with the additional flags\n  `--reuse-values` and `--reset-values`. This means that the `telpresence helm install --upgrade`\n  flag has been deprecated.\n\n- Feature: Image pull secrets for the traffic-agent can now be added using the Helm chart setting\n  `agent.image.pullSecrets`.\n\n- Change: The configmap `traffic-manager-clients` has been renamed to `traffic-manager`.\n\n- Change: The Helm installation will now fail if `intercept.disableGlobal=true` and `traffiManager.mode`\n  is not set to `team`.\n\n- Change: If the cluster is Kubernetes 1.21 or later, the mutating webhook will find the correct namespace\n  using the label `kubernetes.io/metadata.name` rather than `app.kuberenetes.io/name`. Ticket [2913](https://github.com/telepresenceio/telepresence/issues/2913).\n\n- Change: The name of the mutating webhook now contains the namespace of the traffic-manager so\n  that the webhook is easier to identify when there are multiple namespace scoped telepresence\n  installations in the cluster.\n\n- Change: The OSS Helm chart is no longer pushed to the datawire Helm repository. It will\n  instead be pushed from the telepresence proprietary repository. The OSS Helm chart is still\n  what's embedded in the OSS telepresence client. PR [2943](https://github.com/telepresenceio/telepresence/pull/2943).\n\n- Bugfix: Telepresence no longer panics when `--docker-run` is combined with `--name <name>` instead of\n  `--name=<name>`. Ticket [2953](https://github.com/telepresenceio/telepresence/issues/2953).\n\n- Bugfix: Telepresence traffic-manager extracts the cluster domain (e.g. \"cluster.local\") using a CNAME lookup for \"kubernetes.default\"\n  instead of \"kubernetes.default.svc\".\n\n- Bugfix: A timeout was added to the pre-delete hook `uninstall-agents`, so that a helm uninstall doesn't\n  hang when there is no running traffic-manager. PR [2937](https://github.com/telepresenceio/telepresence/pull/2937).\n\n### 2.9.5 (December 8, 2022)\n\n- Security: Update golang to 1.19.4 to address\n  [CVE-2022-41720 and CVE-2022-41717](https://groups.google.com/g/golang-announce/c/L_3rmdT0BMU).\n\n- Bugfix: A regression that was introduced in 2.9.3, preventing use of gce authentication without also\n  having a config element present in the gce configuration in the kubeconfig, has been fixed.\n\n### 2.9.4 (December 5, 2022)\n\n- Feature: The traffic-manager can automatically detect that the node subnets are different from the\n  pod subnets, and switch detection strategy to instead use subnets that cover the pod IPs.\n\n- Bugfix: The `telepresence helm` command `--set x=y` flag didn't correctly set values of other types\n  than `string`. The code now uses standard Helm semantics for this flag.\n\n- Bugfix: Telepresence now uses the correct `agent.image` properties in the Helm chart when copying\n  agent image settings from the `config.yml` file.\n\n- Bugfix: Initialization of FTP type file sharing is delayed, so that setting it using the Helm chart\n  value `intercept.useFtp=true` works as expected.\n\n- Bugfix: The port-forward that is created when Telepresence connects to a cluster is now properly\n  closed when `telepresence quit` is called.\n\n- Bugfix: The user daemon no longer panics when the `config.yml` is modified at a time when the user daemon\n  is running but no session is active.\n\n- Bugfix: Fix race condition that would occur when `telepresence connect` `telepresence leave` was called\n  several times in rapid succession.\n\n### 2.9.3 (November 23, 2022)\n\n- Feature: The helm chart now supports `livenessProbe` and `readinessProbe` for the traffic-manager\n  deployment, so that the pod automatically restarts if it doesn't respond.\n\n- Change: The root daemon now communicates directly with the traffic-manager instead of routing all\n  outbound traffic through the user daemon.\n\n- Change: The output of `telepresence version` is now aligned and no longer contains \"(api v3)\"\n\n- Bugfix: Using `telepresence loglevel LEVEL` now also sets the log level in the root daemon.\n\n- Bugfix: Multi valued kubernetes flags such as `--as-group` are now propagated correctly.\n\n- Bugfix: The root daemon would sometimes hang indefinitely when quit and connect were called\n  in rapid succession.\n\n- Bugfix: Don't use `systemd.resolved` base DNS resolver unless cluster is proxied.\n\n### 2.9.2 (November 16, 2022)\n\n- Bugfix: Fix panic when connecting to an older traffic-manager.\n\n- Bugfix: Fix `http-header` flag sometimes wouldn't propagate correctly.\n\n### 2.9.1 (November 15, 2022)\n\n- Bugfix: Fix regression in 2.9.0 causing `no Auth Provider found for name “gcp”` when connecting.\n\n### 2.9.0 (November 15, 2022)\n\n- Feature: A new `telepresence config view` command was added that shows how the client is currently configured.\n\n- Feature: The traffic-manager can now configure all clients that connect through the `client:` map in\n  the `values.yaml` file.\n\n- Feature: The traffic-manager version is now included in the output from the `telepresence version` command.\n- Feature: add `podLabels` values to Helm Chart to add extra labels to deployment.\n\n- Feature: The telepresence flag `--output` now accepts `yaml` as a valid format.\n\n- Change: The `telepresence status --json` flag is deprecated. Use `telepresence status --output=json` instead.\n\n- Bugfix: Informational messages that don't really originate from the command, such as \"Launching Telepresence Root Daemon\",\n  or \"An update of telepresence ...\", are discarded instead of being printed as plain text before the actual formatted\n  output when using the `--output=json`.\n\n- Bugfix: An attempt to use an invalid value for the global `--output` flag now renders a proper error message.\n\n- Bugfix: Unqualified service names now resolves OK when using `telepresence intercept --docker-run`.\n\n- Bugfix: Files lingering under /etc/resolver on macOS are now removed when a new root daemon starts.\n\n### 2.8.5 (November 2, 2022)\n\n- Change: This is a security release. It's identical with 2.8.3 but built using Go 1.19.3 to address\n  [CVE-2022-41716 and Go issue https://go.dev/issue/56284](https://github.com/golang/go/issues/56284).\n\n### 2.8.4 (November 2, 2022)\n\n- Change: Failed security release. Use 2.8.5.\n\n### 2.8.3 (October 27, 2022)\n\n- Feature: The traffic-manager can be configured to disable global (non-http) intercepts using the\n  Helm chart setting `intercept.disableGlobal`.\n\n- Feature: The port used for the mutating webhook can be configured using the Helm chart setting\n  `agentInjector.webhook.port`.\n\n- Feature: A new repeated `--set a.b.c=v` flag was added to the `telepresence helm install` command so that\n  values can be passed directly from the command line, without first storing them in a file.\n\n- Change: The default port for the mutating webhook is now `443`. It used to be `8443`.\n\n- Change: The traffic-manager will no longer default to use the `tel2` image for the traffic-agent when it is\n  unable to connect to Ambassador Cloud. Air-gapped environments must declare what image to use in the Helm chart.\n\n- Bugfix: `telepresence connect` now works as long as the traffic manager is installed, even if\n  it wasn't installed via `helm install`\n\n- Bugfix: Telepresence check-vpn no longer crashes when the daemons don't start properly.\n\n- Bugfix: The root daemon no longer crashes when the session boot times out before the cluster connection succeeds.\n\n### 2.8.2 (October 15, 2022)\n\n- Feature: The Telepresence DNS resolver is now capable of resolving queries of type `A`, `AAAA`, `CNAME`,\n  `MX`, `NS`, `PTR`, `SRV`, and `TXT`.\n\n- Feature: A new `client` struct was added to the Helm chart. It contains a `connectionTTL` that controls\n  how long the traffic manager will retain a client connection without seeing any sign of life from the client.\n\n- Feature: A `dns` struct container the fields `includeSuffixes` and `excludeSuffixes` was added to the Helm\n  chart `client` struct, making those values configurable per cluster.\n\n- Feature: The API port used by the traffic-manager is now configurable using the Helm chart value `apiPort`.\n  The default port is 8081.\n\n- Change: The Helm chart `dnsConfig` was deprecated but retained for backward compatibility. The fields\n  `alsoProxySubnets` and `neverProxySubnets` can now be found under `routing` in the `client` struct.\n\n- Change: The Helm chart `agentInjector.agentImage` was moved to `agent.image`. The old value is deprecated but\n  retained for backward compatibility.\n\n- Change: The Helm chart `agentInjector.appProtocolStrategy` was moved to `agent.appProtocolStrategy`. The old\n  value is deprecated but retained for backward compatibility.\n\n- Change: The Helm chart `dnsServiceName`, `dnsServiceNamespace`, and `dnsServiceIP` has been removed, because\n  they are no longer needed. The TUN-device will use the traffic-manager pod-IP on platforms where it needs to\n  dedicate an IP for its local resolver.\n\n- Bugfix: Environment variable interpolation now works for all definitions that are copied from pod containers\n  into the injected traffic-agent container.\n\n- Bugfix: An attempt to create simultaneous intercepts that span multiple namespace on the same workstation\n  is detected early and prohibited instead of resulting in failing DNS lookups later on.\n\n- Bugfix: Spurious and incorrect \"\"!! SRV xxx\"\" messages will no longer appear in the logs when the reason\n  is normal context cancellation.\n\n- Bugfix: Single label names now resolves correctly when using Telepresence in Docker on a Linux host\n\n- Bugfix: The Helm chart value `appProtocolStrategy` is now correctly named (used to be `appPortStategy`)\n- Bugfix: Include file name in error message when failing to parse JSON file.\n\n### 2.7.6 (September 16, 2022)\n\n- Reintroduce everything from 2.7.4 with fix for issue preventing the CLI from launching on arm64 builds\n\n### 2.7.5 (September 14, 2022)\n\n- Revert of release 2.7.5 (so essentially the same as 2.7.3)\n\n### 2.7.4 (September 14, 2022)\n\n- Feature: The `resources` for the traffic-agent container and the optional init container can\n  be specified in the Helm chart using the `resource` and `initResource` fields of the\n  `agentInjector.agentImage`.\n\n- Feature: When the traffic-manager fails to inject a traffic-agent, the cause for the failure is\n  detected by reading the cluster events, and propagated to the user.\n\n- Feature: Telepresence can now use an embedded FTP client and load an existing FUSE library\n  instead of running an external `sshfs` or `sshfs-win` binary. This feature is experimental\n  in 2.7.x and enabled by setting `intercept.useFtp` to `true` in the `config.yml`.\n\n- Change: Telepresence on Windows upgraded winfsp from version 1.10 to 1.11\n\n- Bugfix: Running CLI commands on Apple M1 machines will no longer throw warnings about `/proc/cpuinfo`\n  and `/proc/self/auxv`.\n\n### 2.7.3 (September 7, 2022)\n\n- Bugfix: CLI commands that are executed by the user daemon now use a pseudo TTY. This enables\n  `docker run -it` to allocate a TTY and will also give other commands like `bash read` the\n  same behavior as when executed directly in a terminal.\n\n- Bugfix: The traffic-manager will no longer log numerous warnings saying: \"Issuing a\n  systema request without ApiKey or InstallID may result in an error\".\n\n- Bugfix: The traffic-manager will no longer log an error saying: \"Unable to derive subnets\n  from nodes\" when the `podCIDRStrategy` is `auto` and it chooses to instead derive the\n  subnets from the pod IPs.\n\n### 2.7.2 (August 25, 2022)\n\n- Bugfix: Standard I/O is restored when using `telepresence intercept <opts> -- <command>`.\n\n- Bugfix: Graciously handle nil intercept environment from the traffic-manager.\n\n- Feature: The timeout for the initial connectivity check that Telepresence performs\n  in order to determine if the cluster's subnets are proxied or not can now be configured\n  in the `config.yml` file using `timeouts.connectivityCheck`. The default timeout was\n  changed from 5 seconds to 500 milliseconds to speed up the actual connect.\n\n- Feature: Adds cli autocompletion for the `--namespace` flag on the `list` and `intercept` commands,\n  autocompletion for interceptable workloads on the `intercept` command, and autocompletion for\n  active intercepts on the `leave` command.\n\n- Change: The command `telepresence gather-traces` now prints out a message on success.\n- Change: The command `telepresence upload-traces` now prints out a message on success.\n- Change: The command `telepresence gather-traces` now traces itself and reports errors with trace gathering\n\n- Change: The `cli.log` log is now logged at the same level as the `connector.log`\n\n- Bugfix: Streams created between the traffic-agent and the workstation are now properly closed\n  when no interceptor process has been started on the workstation. This fixes a potential problem where\n  a large number of attempts to connect to a non-existing interceptor would cause stream congestion\n  and an unresponsive intercept.\n\n- Bugfix: Telepresence help message functionality without a running user daemon has been restored.\n\n- Bugfix: The `telepresence list` command no longer includes the `traffic-manager` deployment.\n\n### 2.7.1 (August 10, 2022)\n\n- Change: The command `telepresence uninstall` has been restored, but the `--everything` flag is now deprecated.\n\n- Change: `telepresence helm uninstall` will only uninstall the traffic-manager and no longer accepts the `--everything`, `--agent`,\n  or `--all-agents` flags.\n\n- Bugfix: `telepresence intercept` will attempt to connect to the traffic manager before creating an intercept.\n\n### 2.7.0 (August 8, 2022)\n\n- Feature: `telepresence intercept` has gained a\n  `--preview-url-add-request-headers` flag (and `telepresence preview create` a `--add-request-headers` flag) that can be used to inject\n  request headers in to every request made through the preview URL.\n\n- Feature: The Docker image now contains a new program in addition to\n  the existing traffic-manager and traffic-agent: the pod-daemon. The\n  pod-daemon is a trimmed-down version of the user-daemon that is\n  designed to run as a sidecar in a Pod, enabling CI systems to create\n  preview deploys.\n\n- Feature: The Telepresence components now collect OpenTelemetry traces.\n  Up to 10MB of trace data are available at any given time for collection from\n  components. `telepresence gather-traces` is a new command that will collect\n  all that data and place it into a gzip file, and `telepresence upload-traces` is\n  a new command that will push the gzipped data into an OTLP collector.\n\n- Feature: The agent injector now supports a new annotation, `telepresence.getambassador.io/inject-ignore-volume-mounts`, that can be used to make the injector ignore specified volume mounts denoted by a comma-separated string.\n\n- Change: The traffic manager is no longer automatically installed into the cluster. Connecting or creating an intercept in a cluster without a traffic manager will return an error.\n\n- Feature: A new telepresence helm command was added to provide an easy way to install, upgrade, or uninstall the telepresence traffic-manager.\n\n- Change: The command `telepresence uninstall` has been moved to `telepresence helm uninstall`.\n\n- Change: Add an emptyDir volume and volume mount under `/tmp` on the agent sidecar so it works with `readOnlyRootFileSystem: true`\n\n- Feature: Added prometheus support to the traffic manager.\n\n### 2.6.8 (June 23, 2022)\n\n- Feature: The name and namespace for the DNS Service that the traffic-manager uses in DNS auto-detection can now be specified.\n\n- Feature: Should the DNS auto-detection logic in the traffic-manager fail, users can now specify a fallback IP to use.\n\n- Feature: It is now possible to intercept UDP ports with Telepresence and also use `--to-pod` to forward UDP\n  traffic from ports on localhost.\n\n- Change: The Helm chart will now add the `nodeSelector`, `affinity` and `tolerations` values to the traffic-manager's\n  post-upgrade-hook and pre-delete-hook jobs.\n\n- Bugfix: Telepresence no longer fails to inject the traffic agent into the pod generated for workloads that have no\n  volumes and `automountServiceAccountToken: false`.\n\n- Feature: The helm-chart now supports settings resources, securityContext and podSecurityContext for use with chart hooks.\n\n### 2.6.7 (June 22, 2022)\n\n- Bugfix: The Telepresence client will remember and reuse the traffic-manager session after a network failure\n  or other reason that caused an unclean disconnect.\n\n- Bugfix: Telepresence will no longer forward DNS requests for \"wpad\" to the cluster.\n\n- Bugfix: The traffic-agent will properly shut down if one of its goroutines errors.\n\n### 2.6.6 (June 9, 2022)\n\n- Bugfix: The propagation of the `TELEPRESENCE_API_PORT` environment variable now works correctly.\n\n- Bugfix: The `--output json` global flag no longer outputs multiple objects.\n\n### 2.6.5 (June 3, 2022)\n\n- Feature: The `reinvocationPolicy` or the traffic-agent injector webhook can now be configured using the Helm chart.\n\n- Feature: The traffic manager now accepts a root CA for a proxy, allowing it to connect to ambassador cloud from behind an HTTPS proxy.\n  This can be configured through the helm chart.\n\n- Feature: A policy that controls when the mutating webhook injects the traffic-agent was added, and can be configured in the Helm chart.\n\n- Change: Telepresence on Windows upgraded wintun.dll from version 0.12 to version 0.14.1\n\n- Change: Telepresence on Windows upgraded winfsp from version 1.9 to 1.10\n\n- Change: Telepresence upgraded its embedded Helm from version 3.8.1 to 3.9\n\n- Change: Telepresence upgraded its embedded Kubernetes API from version 0.23.4 to 0.24.1\n\n- Feature: Added a `--watch` flag to `telepresence list` that can be used to watch interceptable workloads.\n\n- Change: The configuration setting for `images.webhookAgentImage` is now deprecated. Use `images.agentImage` instead.\n\n- Bugfix: The `reinvocationPolicy` or the traffic-agent injector webhook now defaults to `Never` insteadof `IfNeeded` so\n  that `LimitRange`s on namespaces can inject a missing `resources` element into the injected traffic-agent container.\n\n- Bugfix: UDP based communication with services in the cluster now works as expected.\n\n- Bugfix: The command help will only show Kubernetes flags on the commands that supports them\n\n- Bugfix: Only the errors from the last session will be considered when counting the number of errors in the log after\n  a command failure.\n\n### 2.6.4 (May 23, 2022)\n\n- Bugfix: The traffic-manager RBAC grants permissions to update services, deployments, replicatsets, and statefulsets. Those\n  permissions are needed when the traffic-manager upgrades from versions < 2.6.0 and can be revoked after the upgrade.\n\n### 2.6.3 (May 20, 2022)\n\n- Bugfix: The `--mount` intercept flag now handles relative mount points correctly on non-windows platforms. Windows\n  still require the argument to be a drive letter followed by a colon.\n\n- Bugfix: The traffic-agent's configuration update automatically when services are added, updated or deleted.\n\n- Bugfix: The `--mount` intercept flag now handles relative mount points correctly on non-windows platforms. Windows\n  still require the argument to be a drive letter followed by a colon.\n\n- Bugfix: The traffic-agent's configuration update automatically when services are added, updated or deleted.\n\n- Bugfix: Telepresence will now always inject an initContainer when the service's targetPort is numeric\n\n- Bugfix: Workloads that have several matching services pointing to the same target port are now handled correctly.\n\n- Bugfix: A potential race condition causing a panic when closing a DNS connection is now handled correctly.\n\n- Bugfix: A container start would sometimes fail because and old directory remained in a mounted temp volume.\n\n### 2.6.2 (May 17, 2022)\n\n- Bugfix: Workloads controlled by workloads like Argo `Rollout` are injected correctly.\n\n- Bugfix: Multiple services appointing the same container port no longer result in duplicated ports in an injected pod.\n\n- Bugfix: The `telepresence list` command no longer errors out with \"grpc: received message larger than max\" when listing namespaces\n  with a large number of workloads.\n\n### 2.6.1 (May 16, 2022)\n\n- Bugfix: Telepresence will now handle multiple path entries in the KUBECONFIG environment correctly.\n\n- Bugfix: Telepresence will no longer panic when using preview URLs with traffic-managers < 2.6.0\n\n- Change: Traffic-manager now attempts to obtain a cluster id from the license if it could not obtain it from the Kubernetes API.\n\n### 2.6.0 (May 13, 2022)\n\n- Feature: Traffic-agent is now capable of intercepting multiple containers and multiple ports per container.\n\n- Feature: Telepresence client now require less RBAC permissions in order to intercept.\n\n- Change: All pod-injection is performed by the mutating webhook. Client will no longer modify workloads.\n\n- Change: Traffic-agent is configured using a ConfigMap entry. In prior versions, the configuration was passed in the container environment.\n\n- Change: The helm-chart no longer has a default set for the agentInjector.image.name, and unless its set, the traffic-manager will ask\n  SystemA for the preferred image.\n\n- Change: Client no longer needs RBAC permissions to update deployments, replicasets, and statefulsets.\n\n- Change: Telepresence now uses Helm version 3.8.1 when installing the traffic-manager\n\n- Change: The traffic-manager will not accept connections from clients older than 2.6.0. It can't, because they still use the old way of\n  injecting the agent by modifying the workload.\n\n- Change: When upgrading, all workloads with injected agents will have their agent \"uninstalled\" automatically. The mutating webhook will\n  then ensure that their pods will receive an updated traffic-agent.\n\n- Bugfix: Remote mounts will now function correctly with custom `securityContext`.\n\n- Bugfix: The help for commands that accept kubernetes flags will now display those flags in a separate group.\n\n- Bugfix: Using `telepresence leave` or `telepresence quit` on an intercept that spawned a command using `--` on the command line\n  will now terminate that command since it's considered parented by the intercept that is removed.\n\n- Change: Add support for structured output as JSON by setting the global --output=json flag.\n\n### 2.5.8 (April 27, 2022)\n\n- Bugfix: Telepresence now ensures that the download folder for the enhanced free client is created prior to downloading it.\n\n### 2.5.7 (April 25, 2022)\n\n- Change: A namespaced traffic-manager will no longer require cluster wide RBAC. Only Roles and RoleBindings are now used.\n\n- Bugfix: The DNS recursion detector didn't work correctly on Windows, resulting in sporadic failures to resolve names\n  that were resolved correctly at other times.\n\n- Bugfix: A telepresence session will now last for 24 hours after the user's last connectivity. If a session expires, the connector will automatically try to reconnect.\n\n### 2.5.6 (April 15, 2022)\n\n- Bugfix: The `gather-logs` command will no longer send any logs through `gRPC`.\n\n- Change: Telepresence agents watcher will now only watch namespaces that the user has accessed since the last `connect`.\n\n### 2.5.5 (April 8, 2022)\n\n- Change: The traffic-manager now requires permissions to read pods across namespaces even if installed with limited permissions\n\n- Bugfix: The DNS resolver used on Linux with systemd-resolved now flushes the cache when the search path changes.\n\n- Bugfix: The `telepresence list` command will produce a correct listing even when not preceded by a `telepresence connect`.\n\n- Bugfix: The root daemon will no longer get into a bad state when a disconnect is rapidly followed by a new connect.\n\n- Bugfix: The client will now only watch agents from accessible namespaces, and is also constrained to namespaces explicitly mapped\n  using the `connect` command's `--mapped-namespaces` flag.\n\n- Bugfix: The `gather-logs` command will only gather traffic-agent logs from accessible namespaces, and is also constrained to namespaces\n  explicitly mapped using the `connect` command's `--mapped-namespaces` flag.\n\n### 2.5.4 (March 29, 2022)\n\n- Change: The list command, when used with the `--intercepts` flag, will list the users intercepts from all namespaces\n\n- Change: The status command includes the install id, user id, account id, and user email in its result, and can print output as JSON\n\n- Change: The lookup-timeout config flag used to set timeouts for DNS queries resolved by a cluster now also configures the timeout for fallback queries (i.e. queries not resolved by the cluster) when connected to the cluster.\n\n- Change: The TUN device will no longer route pod or service subnets if it is running in a machine that's already connected to the cluster\n\n- Bugfix: The client's gather logs command and agent watcher will now respect the configured grpc.maxReceiveSize\n\n- Bugfix: Client and agent sessions no longer leaves dangling waiters in the traffic-manager when they depart.\n\n- Bugfix: An advice to \"see logs for details\" is no longer printed when the argument count is incorrect in a CLI command.\n\n- Bugfix: Removed a bad concatenation that corrupted the output path of `telepresence gather-logs`.\n\n- Bugfix: Agent container is no longer sensitive to a random UID or an UID imposed by a SecurityContext.\n\n- Bugfix: Intercepts that fail to create are now consistently removed to prevent non-working dangling intercepts from sticking around.\n\n- Bugfix: The ingress-l5 flag will no longer be forcefully set to equal the --ingress-host flag\n\n- Bugfix: The DNS fallback resolver on Linux now correctly handles concurrent requests without timing them out\n\n### 2.5.3 (February 25, 2022)\n\n- Feature: Client-side binaries for the arm64 architecture are now available for linux\n\n- Bugfix: Fixed bug in the TCP stack causing timeouts after repeated connects to the same address\n\n### 2.5.2 (February 23, 2022)\n\n- Bugfix: Fixed a bug where Telepresence would use the last server in resolv.conf\n\n### 2.5.1 (February 19, 2022)\n\n- Bugfix: Fixed a bug where using a GKE cluster would error with: No Auth Provider found for name \"gcp\"\n\n### 2.5.0 (February 18, 2022)\n\n- Feature: The flags `--http-path-equal`, `--http-path-prefix`, and `--http-path-regex` can can be used in addition to the `--http-match`\n  flag to filter personal intercepts by the request URL path\n\n- Feature: The flag `--http-meta` can be used to declare metadata key value pairs that will be returned by the Telepresence rest API\n  endpoint /intercept-info\n\n- Feature: Telepresence Login now prompts you to optionally install an enhanced free client, which has some additional features when used with Ambassador Cloud.\n\n- Change: Logs generated by the CLI are no longer discarded. Instead, they will end up in `cli.log`.\n\n- Change: Both daemon logfiles now rotate daily instead of once for each new connect\n\n- Change: The flag `--http-match` was renamed to `--http-header`. Old flag still works, but is deprecated and doesn't\n  show up in the help.\n\n- Change: The verb \"watch\" was added to the set of required verbs when accessing services and workloads for the client RBAC ClusterRole\n\n- Change: Telepresence is no longer backward compatible with versions 2.4.4 or older because the deprecated multiplexing tunnel functionality was removed.\n\n- Change: The global networking flags are no longer global. Using them will render a deprecation warning unless they are supported by the command.\n  The subcommands that support networking flags are `connect`, `current-cluster-id`, and `genyaml`.\n\n- Change: Telepresence now includes GOARCH of the binary in the metadata reported.\n\n- Bugfix: The also-proxy and never-proxy subnets are now displayed correctly when using the `telepresence status` command\n\n- Bugfix: Telepresence will no longer require `SETENV` privileges when starting the root daemon.\n\n- Bugfix: Telepresence will now parse device names containing dashes correctly when determining routes that it should never block.\n\n- Bugfix: The cluster domain (typically \"cluster.local\") is no longer added to the DNS `search` on Linux using `systemd-resolved`. Instead,\n  it is added as a `domain` so that names ending with it are routed to the DNS server.\n\n- Bugfix: Fixed a bug where the `--json` flag did not output json for `telepresence list` when there were no workloads.\n\n- Change: Updated README file with more details about the project.\n\n- Bugfix: Fixed a bug where the overriding DNS resolver would break down in Linux if /etc/resolv.conf listed an ipv6 resolver\n\n### 2.4.11 (February 10, 2022)\n\n- Change: Include goarch metadata for reporting to distinguish between Intel and Apple Silicon Macs\n\n### 2.4.10 (January 13, 2022)\n\n- Feature: The flag `--http-plaintext` can be used to ensure that an intercept uses plaintext http or grpc when\n  communicating with the workstation process.\n\n- Feature: The port used by default in the `telepresence intercept` command (8080), can now be changed by setting\n  the `intercept.defaultPort` in the `config.yml` file.\n\n- Feature: The strategy when selecting the application protocol for personal intercepts in agents injected by the\n  mutating webhook can now be configured using the `agentInjector.appProtocolStrategy` in the Helm chart.\n\n- Feature: The strategy when selecting the application protocol for personal intercepts can now be configured using\n  the `intercept.appProtocolStrategy` in the `config.yml` file.\n\n- Change: Telepresence CI now runs in GitHub Actions instead of Circle CI.\n\n- Bugfix: Telepresence will no longer log invalid: \"unhandled connection control message: code DIAL_OK\" errors.\n\n- Bugfix: User will not be asked to log in or add ingress information when creating an intercept until a check has been\n  made that the intercept is possible.\n\n- Bugfix: Output to `stderr` from the traffic-agent's `sftp` and the client's `sshfs` processes are properly logged as errors.\n\n- Bugfix: Auto installer will no longer not emit backslash separators for the `/tel-app-mounts` paths in the\n  traffic-agent container spec when running on Windows\n\n### 2.4.9 (December 9, 2021)\n\n- Bugfix: Fixed an error where access tokens were not refreshed if you log in\n  while the daemons are already running.\n\n- Bugfix: A helm upgrade using the --reuse-values flag no longer fails on a \"nil pointer\" error caused by a nil `telpresenceAPI` value.\n\n### 2.4.8 (December 3, 2021)\n\n- Feature: A RESTful service was added to Telepresence, both locally to the client and to the `traffic-agent` to help determine if messages with a set of headers should be\n  consumed or not from a message queue where the intercept headers are added to the messages.\n\n- Change: The environment variable TELEPRESENCE_LOGIN_CLIENT_ID is no longer used.\n\n- Feature: There is a new subcommand, `test-vpn`, that can be used to diagnose connectivity issues with a VPN.\n\n- Bugfix: The tunneled network connections between Telepresence and\n  Ambassador Cloud now behave more like ordinary TCP connections,\n  especially around timeouts.\n\n### 2.4.7 (November 24, 2021)\n\n- Feature: The agent injector now supports a new annotation, `telepresence.getambassador.io/inject-service-name`, that can be used to set the name of the service to be intercepted.\n  This will help disambiguate which service to intercept for when a workload is exposed by multiple services, such as can happen with Argo Rollouts\n\n- Feature: The kubeconfig extensions now support a `never-proxy` argument, analogous to `also-proxy`, that defines a set of subnets that will never be proxied via telepresence.\n\n- Feature: Added flags to \"telepresence intercept\" that set the ingress fields as an alternative to using the dialogue.\n\n- Change: Telepresence check the versions of the client and the daemons and ask the user to quit and restart if they don't match.\n\n- Change: Telepresence DNS now uses a very short TTL instead of explicitly flushing DNS by killing the `mDNSResponder` or doing `resolvectl flush-caches`\n\n- Bugfix: Legacy flags such as `--swap-deployment` can now be used together with global flags.\n\n- Bugfix: Outbound connections are now properly closed when the peer closes.\n\n- Bugfix: The DNS-resolver will trap recursive resolution attempts (may happen when the cluster runs in a docker-container on the client).\n\n- Bugfix: The TUN-device will trap failed connection attempts that results in recursive calls back into the TUN-device (may happen when the\n  cluster runs in a docker-container on the client).\n\n- Bugfix: Fixed a potential deadlock when a new agent joined the traffic manager.\n\n- Bugfix: The app-version value of the Helm chart embedded in the telepresence binary is now automatically updated at build time. The value is hardcoded in the\n  original Helm chart when we release so this fix will only affect our nightly builds.\n\n- Bugfix: The configured webhookRegistry is now propagated to the webhook installer even if no webhookAgentImage has been set.\n\n- Bugfix: Login logs the user in when their access token has expired, instead of having no effect.\n\n### 2.4.6 (November 2, 2021)\n\n- Feature: Telepresence CLI is now built and published for Apple Silicon Macs.\n\n- Feature: Telepresence now supports manually injecting the traffic-agent YAML into workload manifests.\n  Use the `genyaml` command to create the sidecar YAML, then add the `telepresence.getambassador.io/manually-injected: \"true\"` annotation to your pods to allow Telepresence to intercept them.\n\n- Feature: Added a json flag for the \"telepresence list\" command. This will aid automation.\n\n- Change: `--help` text now includes a link to https://www.telepresence.io/ so users who download Telepresence via Brew or some other mechanism are able to find the documentation easily.\n\n- Bugfix: Telepresence will no longer attempt to proxy requests to the API server when it happens to have an IP address within the CIDR range of pods/services.\n\n### 2.4.5 (October 15, 2021)\n\n- Feature: Intercepting headless services is now supported. It's now possible to request a headless service on whatever port it exposes and get a response from the intercept.\n\n- Feature: Preview url questions have more context and provide \"best guess\" defaults.\n\n- Feature: The `gather-logs` command added two new flags. One to anonymize pod names + namespaces and the other for getting the pod yaml of the `traffic-manager` and any pod that contains a `traffic-agent`.\n\n- Change: Use one tunnel per connection instead of multiplexing into one tunnel. This client will still be backwards compatible with older `traffic-manager`s that only support multiplexing.\n\n- Bugfix: Telepresence will now log that the kubernetes server version is unsupported when using a version older than 1.17.\n\n- Bugfix: Telepresence only adds the security context when necessary: intercepting a headless service or using a numeric port with the webhook agent injector.\n\n### 2.4.4 (September 27, 2021)\n\n- Feature: The strategy used by traffic-manager's discovery of pod CIDRs can now be configured using the Helm chart.\n\n- Feature: Add the command `telepresence gather-logs`, which bundles the logs for all components\n  into one zip file that can then be shared in a GitHub issue, in slack, etc. Use\n  `telepresence gather-logs --help` to see additional options for running the command.\n\n- Feature: The agent injector now supports injecting Traffic Agents into pods that have unnamed ports.\n\n- Bugfix: The traffic-manager now uses less CPU-cycles when computing the pod CIDRs.\n\n- Bugfix: If a deployment annotated with webhook annotations is deployed before telepresence is installed, telepresence will now install an agent in that deployment before intercept\n\n- Bugfix: Fix an issue where the traffic-manager would sometimes go into a CPU loop.\n\n- Bugfix: The TUN-device no longer builds an unlimited internal buffer before sending it when receiving lots of TCP-packets without PSH.\n  Instead, the buffer is flushed when it reaches a size of 64K.\n\n- Bugfix: The user daemon would sometimes hang when it encountered a problem connecting to the cluster or the root daemon.\n\n- Bugfix: Telepresence correctly reports an intercept port conflict instead of panicking with segfault.\n\n### 2.4.3 (September 15, 2021)\n\n- Feature: The environment variable `TELEPRESENCE_INTERCEPT_ID` is now available in the interceptor's environment.\n\n- Bugfix: A timing related bug was fixed that sometimes caused a \"daemon did not start\" failure.\n\n- Bugfix: On Windows, crash stack traces and other errors were not\n  written to the log files, now they are.\n\n- Bugfix: On Linux kernel 4.11 and above, the log file rotation now\n  properly reads the birth-time of the log file. On older kernels, it\n  continues to use the old behavior of using the change-time in place\n  of the birth-time.\n\n- Bugfix: Telepresence will no longer refer the user to the daemon logs for errors that aren't related to\n  problems that are logged there.\n\n- Bugfix: The overriding DNS resolver will no longer apply search paths when resolving \"localhost\".\n\n- Bugfix: The cluster domain used by the DNS resolver is retrieved from the traffic-manager instead of being\n  hard-coded to \"cluster.local\".\n\n- Bugfix: \"Telepresence uninstall --everything\" now also uninstalls agents installed via mutating webhook\n\n- Bugfix: Downloading large files during an intercept will no longer cause timeouts and hanging traffic-agent.\n\n- Bugfix: Passing false to the intercept command's --mount flag will no longer result in a filesystem being mounted.\n\n- Bugfix: The traffic manager will establish outbound connections in parallel instead of sequentially.\n\n- Bugfix: The `telepresence status` command reports correct DNS settings instead of \"Local IP: nil, Remote IP: nil\"\n\n### 2.4.2 (September 1, 2021)\n\n- Feature: A new `telepresence loglevel <level>` subcommand was added that enables changing the loglevel\n  temporarily for the local daemons, the `traffic-manager` and the `traffic-agents`.\n\n- Change: The default log-level is now `info` for all components of Telepresence.\n\n- Bugfix: The overriding DNS resolver will no longer apply search paths when resolving \"localhost\".\n\n- Bugfix: The RBAC was not updated in the helm chart to enable the traffic-manager to `get` and `list`\n  namespaces, which would impact users who use licensed features of the Telepresence extensions in an\n  air-gapped environment.\n\n- Bugfix: The timeout for Helm actions wasn't always respected which could cause a failing install of the\n  `traffic-manager` to make the user daemon to hang indefinitely.\n\n### 2.4.1 (August 30, 2021)\n\n- Bugfix: Telepresence will now mount all directories from `/var/run/secrets`, not just the kubernetes.io ones.\n  This allows the mounting of secrets directories such as eks.amazonaws.com (for IRSA tokens)\n\n- Bugfix: The grpc.maxReceiveSize setting is now correctly propagated to all grpc servers.\n  This allows users to mitigate a root daemon crash when sending a message over the default maximum size.\n\n- Bugfix: Some slight fixes to the `homebrew-package.sh` script which will enable us to run\n  it manually if we ever need to make homebrew point at an older version.\n\n- Feature: Helm chart has now a feature to on demand regenerate certificate used for mutating webhook by setting value.\n  `agentInjector.certificate.regenerate`\n\n- Change: The traffic-manager now requires `get` namespace permissions to get the cluster ID instead of that value being\n  passed in as an environment variable to the traffic-manager's deployment.\n\n- Change: The traffic-manager is now installed via an embedded version of the Helm chart when `telepresence connect` is first performed on a cluster.\n  This change is transparent to the user.\n  A new configuration flag, `timeouts.helm` sets the timeouts for all helm operations performed by the Telepresence binary.\n\n- Bugfix: Telepresence will initialize the default namespace from the kubeconfig on each call instead of just doing it when connecting.\n\n- Bugfix: The timeout to keep idle outbound TCP connections alive was increased from 60 to 7200 seconds which is the same as\n  the Linux `tcp_keepalive_time` default.\n\n- Bugfix: Telepresence will now remove a socket that is the result of an ungraceful termination and retry instead of printing\n  an error saying \"this usually means that the process has terminated ungracefully\"\n\n- Change: Failure to report metrics is logged using loglevel info rather than error.\n\n- Bugfix: A potential deadlock situation is fixed that sometimes caused the user daemon to hang when the user\n  was logged in.\n\n- Feature: The scout reports will now include additional metadata coming from environment variables starting with\n  `TELEPRESENCE_REPORT_`.\n\n- Bugfix: The config setting `images.agentImage` is no longer required to contain the repository. The repository is\n  instead picked from `images.repository`.\n\n- Change: The `registry`, `webhookRegistry`, `agentImage` and `webhookAgentImage` settings in the `images` group of the `config.yml`\n  now get their defaults from `TELEPRESENCE_AGENT_IMAGE` and `TELEPRESENCE_REGISTRY`.\n\n### 2.4.0 (August 4, 2021)\n\n- Feature: There is now a native Windows client for Telepresence.\n  All the same features supported by the macOS and Linux client are available on Windows.\n\n- Feature: Telepresence can now receive messages from the cloud and raise\n  them to the user when they perform certain commands.\n\n- Bugfix: Initialization of `systemd-resolved` based DNS sets\n  routing domain to improve stability in non-standard configurations.\n\n- Bugfix: Edge case error when targeting a container by port number.\n  Before if your matching/target container was at containers list index 0,\n  but if there was a container at index 1 with no ports, then the\n  \"no ports\" container would end up the selected one\n\n- Bugfix: A `$(NAME)` reference in the agent's environment will now be\n  interpolated correctly.\n\n- Bugfix: Telepresence will no longer print an INFO level log message when\n  no config.yml file is found.\n\n- Bugfix: A panic is no longer raised when passing an argument to the\n  `telepresence intercept` option `--http-match` that doesn't contain an\n  equal sign.\n\n- Bugfix: The `traffic-manager` will only send subnet updates to a\n  client root daemon when the subnets actually change.\n\n- Bugfix: The agent uninstaller now distinguishes between recoverable\n  and unrecoverable failures, allowing uninstallation from manually changed\n  resources\n\n### 2.3.7 (July 23, 2021)\n\n- Feature: An `also-proxy` entry in the Kubernetes cluster config will\n  show up in the output of the `telepresence status` command.\n\n- Feature: `telepresence login` now has an `--apikey=KEY` flag that\n  allows for non-interactive logins. This is useful for headless\n  environments where launching a web-browser is impossible, such as\n  cloud shells, Docker containers, or CI.\n\n- Bugfix: Dialer will now close if it gets a ConnectReject. This was\n  encountered when doing an intercept without a local process running\n  and would result in requests hanging indefinitely.\n\n- Bugfix: Made `telepresence list` command faster.\n\n- Bugfix: Mutating webhook injector correctly hides named ports for probes.\n\n- Bugfix: Initialization of `systemd-resolved` based DNS is more stable and\n  failures causing telepresence to default to the overriding resolver will no\n  longer cause general DNS lookup failures.\n\n- Bugfix: Fixed a regression introduced in 2.3.5 that caused `telepresence current-cluster-id`\n  to crash.\n\n- Bugfix: New API keys generated internally for communication with\n  Ambassador Cloud no longer show up as \"no description\" in the\n  Ambassador Cloud web UI. Existing API keys generated by older\n  versions of Telepresence will still show up this way.\n\n- Bugfix: Fixed a race condition that logging in and logging out\n  rapidly could cause memory corruption or corruption of the\n  `user-info.json` cache file used when authenticating with Ambassador\n  Cloud.\n\n### 2.3.6 (July 20, 2021)\n\n- Bugfix: Fixed a regression introduced in 2.3.5 that caused preview\n  URLs to not work.\n\n- Bugfix: Fixed a regression introduced in 2.3.5 where the Traffic\n  Manager's `RoleBinding` did not correctly appoint the\n  `traffic-manager` `Role`, causing subnet discovery to not be able to\n  work correctly.\n\n- Bugfix: Fixed a regression introduced in 2.3.5 where the root daemon\n  did not correctly read the configuration file; ignoring the user's\n  configured log levels and timeouts.\n\n- Bugfix: Fixed an issue that could cause the user daemon to crash\n  during shutdown, as during shutdown it unconditionally attempted to\n  close a channel even though the channel might already be closed.\n\n### 2.3.5 (July 15, 2021)\n\n- Feature: Telepresence no longer depends on having an external\n  `kubectl` binary, which might not be present for OpenShift users\n  (who have `oc` instead of `kubectl`).\n- Feature: `skipLogin` can be used in the config.yml to tell the cli not to connect to cloud when using an air-gapped environment.\n- Feature: The Telepresence Helm chart now supports installing multiple\n  Traffic Managers in multiple namespaces. This will allow operators to\n  install Traffic Managers with limited permissions that match the\n  permissions restrictions that Telepresence users are subject to.\n- Feature: The maximum size of messages that the client can receive over gRPC can now be configured. The gRPC default of 4MB isn't enough\n  under some circumstances.\n- Change: `TELEPRESENCE_AGENT_IMAGE` and `TELEPRESENCE_REGISTRY` are now only configurable via config.yml.\n- Bugfix: Fixed and improved several error messages, to hopefully be\n  more helpful.\n- Bugfix: Fixed a DNS problem on macOS causing slow DNS lookups when connecting to a local cluster.\n\n### 2.3.4 (July 9, 2021)\n\n- Bugfix: Some log statements that contained garbage instead of a proper IP address now produce the correct address.\n- Bugfix: Telepresence will no longer panic when multiple services match a workload.\n- Bugfix: The traffic-manager will now accurately determine the service subnet by creating a dummy-service in its own namespace.\n- Bugfix: Telepresence connect will no longer try to update the traffic-manager's clusterrole if the live one is identical to the desired one.\n- Bugfix: The Telepresence helm chart no longer fails when installing with `--set clientRbac.namespaced=true`\n\n### 2.3.3 (July 7, 2021)\n\n- Feature: Telepresence now supports installing the Traffic Manager\n  via Helm. This will make it easy for operators to install and\n  configure the server-side components of Telepresence separately from\n  the CLI (which in turn allows for better separation of permissions).\n\n- Feature: As the `traffic-manager` can now be installed in any\n  namespace via Helm, Telepresence can now be configured to look for\n  the traffic manager in a namespace other than `ambassador`. This\n  can be configured on a per-cluster basis.\n\n- Feature: `telepresence intercept` now supports a `--to-pod` flag\n  that can be used to port-forward sidecars' ports from an intercepted\n  pod\n\n- Feature: `telepresence status` now includes more information about\n  the root daemon.\n\n- Feature: We now do nightly builds of Telepresence for commits on release/v2 that haven't been tagged and published yet.\n\n- Change: Telepresence no longer automatically shuts down the old\n  `api_version=1` `edgectl` daemon. If migrating from such an old\n  version of `edgectl` you must now manually shut down the `edgectl`\n  daemon before running Telepresence. This was already the case when\n  migrating from the newer `api_version=2` `edgectl`.\n\n- Bugfix: The root daemon no longer terminates when the user daemon\n  disconnects from its gRPC streams, and instead waits to be\n  terminated by the CLI. This could cause problems with things not\n  being cleaned up correctly.\n\n- Bugfix: An intercept will survive deletion of the intercepted pod\n  provided that another pod is created (or already exists) that can\n  take over.\n\n### 2.3.2 (June 18, 2021)\n\n- Feature: The mutator webhook for injecting traffic-agents now\n  recognizes a `telepresence.getambassador.io/inject-service-port`\n  annotation to specify which port to intercept; bringing the\n  functionality of the `--port` flag to users who use the mutator\n  webook in order to control Telepresence via GitOps.\n\n- Feature: Outbound connections are now routed through the intercepted\n  Pods which means that the connections originate from that Pod from\n  the cluster's perspective. This allows service meshes to correctly\n  identify the traffic.\n\n- Change: Inbound connections from an intercepted agent are now\n  tunneled to the manager over the existing gRPC connection, instead\n  of establishing a new connection to the manager for each inbound\n  connection. This avoids interference from certain service mesh\n  configurations.\n\n- Change: The traffic-manager requires RBAC permissions to list Nodes,\n  Pods, and to create a dummy Service in the manager's namespace.\n\n- Change: The on-laptop client no longer requires RBAC permissions to\n  list Nodes in the cluster or to create Services, as that\n  functionality has been moved to the traffic-manager.\n\n- Bugfix: Telepresence will now detect the pod CIDR ranges even if\n  they are not listed in the Nodes.\n\n- Bugfix: The list of cluster subnets that the virtual network\n  interface will route is now configured dynamically and will follow\n  changes in the cluster.\n\n- Bugfix: Subnets fully covered by other subnets are now pruned\n  internally and thus never superfluously added to the laptop's\n  routing table.\n\n- Change: The `trafficManagerAPI` timout default has changed from 5\n  seconds to 15 seconds, in order to facilitate the extended time it\n  takes for the traffic-manager to do its initial discovery of cluster\n  info as a result of the above bugfixes.\n\n- Bugfix: On macOS, files generated under `/etc/resolver/` as the\n  result of using `include-suffixes` in the cluster config are now\n  properly removed on quit.\n\n- Bugfix: Telepresence no longer erroneously terminates connections\n  early when sending a large HTTP response from an intercepted\n  service.\n\n- Bugfix: When shutting down the user-daemon or root-daemon on the\n  laptop, `telepresence quit` and related commands no longer return\n  early before everything is fully shut down. Now it can be counted\n  on that by the time the command has returned that all the\n  side-effects on the laptop have been cleaned up.\n\n### 2.3.1 (June 14, 2021)\n\n- Feature: Agents can now be installed using a mutator webhook\n- Feature: DNS resolver can now be configured with respect to what IP addresses that are used, and what lookups that gets sent to the cluster.\n- Feature: Telepresence can now be configured to proxy subnets that aren't part of the cluster but only accesible from the cluster.\n- Change: The `trafficManagerConnect` timout default has changed from 20 seconds to 60 seconds, in order to facilitate\n  the extended time it takes to apply everything needed for the mutator webhook.\n- Change: Telepresence is now installable via `brew install datawire/blackbird/telepresence`\n- Bugfix: Fix a bug where sometimes large transfers from services on the cluster would hang indefinitely\n\n### 2.3.0 (June 1, 2021)\n\n- Feature: Telepresence is now installable via brew\n- Feature: `telepresence version` now also includes the version of the currently running user daemon.\n- Change: A TUN-device is used instead of firewall rules for routing outbound connections.\n- Change: Outbound connections now use gRPC instead of ssh, and the traffic-manager no longer has a sshd running.\n- Change: The traffic-agent no longer has a sshd running. Remote volume mounts use sshfs in slave mode, talking directly to sftp.\n- Change: The local DNS now routes the name lookups to intercepted agents or traffic-manager.\n- Change: The default log-level for the traffic-manager and the root-daemon was changed from \"debug\" to \"info\".\n- Change: The command line is now statically-linked, so it is usable on systems with different libc's.\n- Bugfix: Using --docker-run no longer fail to mount remote volumes when docker runs as root.\n- Bugfix: Fixed a number of race conditions.\n- Bugfix: Fix a crash when there is an error communicating with the traffic-manager about Ambassador Cloud.\n- Bugfix: Fix a bug where sometimes when displaying or logging a timeout error it fails to determine which configurable timeout is responsible.\n- Bugfix: The root-user daemon now respects the timeouts in the normal user's configuration file.\n\n### 2.2.2 (May 17, 2021)\n\n- Feature: Telepresence translates legacy Telepresence commands into viable Telepresence commands.\n- Bugfix: Intercepts will only look for agents that are in the same namespace as the intercept.\n\n### 2.2.1 (April 29, 2021)\n\n- Bugfix: Improve `ambassador` namespace detection that was trying to create the namespace even when the namespace existed, which was an undesired RBAC escalation for operators.\n- Bugfix: Telepresence will now no longer generate excessive traffic trying repeatedly to exchange auth tokens with Ambassador Cloud. This could happen when upgrading from <2.1.4 if you had an expired `telepresence login` from before upgrading.\n- Bugfix: `telepresence login` now correctly handles expired logins, just like all of the other subcommands.\n\n### 2.2.0 (April 19, 2021)\n\n- Feature: `telepresence intercept` now has the option `--docker-run` which will start a docker container with intercepted environment and volume mounts.\n- Bugfix: `telepresence uninstall` can once again uninstall agents installed by older versions of Telepresence.\n- Feature: Addition of `telepresence current-cluster-id` and `telepresence license` commands for using licenses with the Ambassador extension, primarily in air-gapped environments.\n\n### 2.1.5 (April 12, 2021)\n\n- Feature: When intercepting `--port` now supports specifying a service port or a service name. Previously, only service name was supported.\n- Feature: Intercepts using `--mechanism=http` now support mTLS.\n- Bugfix: One of the log messages was using the incorrect variable, which led to misleading error messages on `telepresence uninstall`.\n- Bugfix: Telepresence no longer generates port names longer than 15 characters.\n\n### 2.1.4 (April 5, 2021)\n\n- Feature: `telepresence status` has been enhanced to provide more information. In particular, it now provides separate information on the daemon and connector processes, as well as showing login status.\n- Feature: Telepresence now supports intercepting StatefulSets\n- Change: Telepresence necessary RBAC has been refined to support StatefulSets and now requires \"get,list,update\" for StatefulSets\n- Change: Telepresence no longer requires that port 1080 must be available.\n- Change: Telepresence now makes use of refresh tokens to avoid requiring the user to manually log in so often.\n- Bugfix: Fix race condition that occurred when intercepting a ReplicaSet while another pod was terminating in the same namespace (this fixes a transient test failure)\n- Bugfix: Fix error when intercepting a ReplicaSet requires the containerPort to be hidden.\n- Bugfix: `telepresence quit` no longer starts the daemon process just to shut it down.\n- Bugfix: Telepresence no longer hangs the next time it's run after getting killed.\n- Bugfix: Telepresence now does a better job of automatically logging in as necessary, especially with regard to expired logins.\n- Bugfix: Telepresence was incorrectly looking across all namespaces for services when intercepting, but now it only looks in the given namespace. This should prevent people from running into \"Found multiple services\" errors when services with the same selectors existed in other namespaces.\n\n### 2.1.3 (March 29, 2021)\n\n- Feature: Telepresence now supports intercepting ReplicaSets (that aren't owned by a Deployment)\n- Change: The --deployment (-d) flag is now --workload (-w), as we start supporting more workloads than just Deployments\n- Change: Telepresence necessary RBAC has changed and now requires \"delete\" for Pods and \"get,list,update\" for ReplicaSets\n- Security: Upgrade to a newer OpenSSL, to address OpenSSL CVE-2021-23840.\n- Bugfix: Connecting to Minikube/Hyperkit no longer fails intermittently.\n- Bugfix: Telepresence will now make /var/run/secrets/kubernetes.io available when mounting remote volumes.\n- Bugfix: Hiccups in the connection to the cluster will no longer cause the connector to shut down; it now retries properly.\n- Bugfix: Fix a crash when binary dependencies are missing.\n- Bugfix: You can now specify a service when doing an intercept (--service), this is useful if you have two services that select on the same labels (e.g. If using Argo Rollouts do deployments)\n\n### 2.1.2 (March 19, 2021)\n\n- Bugfix: Uninstalling agents now only happens once per deployment instead of once per agent.\n- Bugfix: The list command no longer shows agents from namespaces that aren't mapped.\n- Bugfix: IPv6 routes now work and don't prevent other pfctl rules being written in macOS\n- Bugfix: Pods with `hostname` and/or `subdomain` now get correct DNS-names and routes.\n- Change: Service UID was added to InterceptSpec to better link intercepts and services.\n- Feature: All timeouts can now be configured in a <user-config-dir>/telepresence/config.yml file\n\n### 2.1.1 (March 12, 2021)\n\n- Bugfix: When looking at the container to intercept, it will check if there's a better match before using a container without containerPorts.\n- Bugfix: Telepresence will now map `kube-*` and `ambassador` namespaces by default.\n- Bugfix: Service port declarations that lack a TargetPort field will now correctly default to using the Port field instead.\n- Bugfix: Several DNS fixes. Notably, introduce a fake \"tel2-search\" domain that gets replaced with a dynamic DNS search when queried, which fixes DNS for Docker with no `-net host`.\n- Change: Improvements to how we report the requirements for volume mounts; notably, if the requirements are not met then it defaults to `--mount=false`.\n- Change: There has been substantial code cleanup in the \"connector\" process.\n\n### 2.1.0 (March 8, 2021)\n\n- Feature: Support headless services (including ExternalName), which you can use if you used \"Also Proxy\" in telepresence 1.\n- Feature: Preview URLs can now set a layer-5 hostname (TLS-SNI and HTTP \"Host\" header) that is different than the layer-3 hostname (IP-address/DNS-name) that is used to dial to the ingress.\n- Feature: The Ingress info will now contain a layer-5 hostname that can be used for TLS-SLI and HTTP \"Host\" header when accessing a service.\n- Feature: Users can choose which port to intercept when intercepting a service with multiple ports.\n- Bugfix: Environment variables declared with `envFrom` in the app-container are now propagated correctly to the client during intercept.\n- Bugfix: The description of the `--everything` flag for the `uninstall` command was corrected.\n- Bugfix: Connecting to a large cluster could take a very long time and even make the process hang. This is no longer the case.\n- Bugfix: Telepresence now explicitly requires macFUSE version 4.0.5 or higher for macOS.\n- Bugfix: A `tail -F <daemon log file>` no longer results in a \"Permission denied\" when reconnecting to the cluster.\n- Change: The telepresence daemon will no longer use port 1234 for the firewall-to-SOCKS server, but will instead choose an available port dynamically.\n- Change: On connect, telepresence will no longer suggest the `--mapped-namespaces` flag when the user connects to a large cluster.\n\n### 2.0.3 (February 24, 2021)\n\n- Feature: There is now an extension mechanism where you can tell Telepresence about different agents and what arguments they support. The new `--mechanism` flag can explicitly identify which extension to use.\n- Feature: An intercept of `NAME` that is made using `--namespace=NAMESPACE` but not using `--deployment` will use `NAME` as the name of the deployment and `NAME-NAMESPACE` as the name of the intercept.\n- Feature: Declare a local-only intercept for the purpose of getting direct outbound access to the intercept's namespace using boolean flag `--local-only`.\n- Bugfix: Fix a regression in the DNS resolver that prevented name resolution using NAME.NAMESPACE. Instead, NAME.NAMESPACE.svc.cluster.local was required.\n- Bugfix: Fixed race-condition in the agent causing attempts to dial to `:0`.\n- Bugfix: It is now more strict about which agent versions are acceptable and will be more eager to apply upgrades.\n- Change: Related to things now being in extensions, the `--match` flag has been renamed to `--http-match`.\n- Change: Cluster connection timeout has been increased from 10s to 20s.\n- Change: On connect, if telepresence detects a large cluster, it will suggest the `--mapped-namespaces` flag to the user as a way to speed it up.\n- Change: The traffic-agent now has a readiness probe associated with its container.\n\n### 2.0.2 (February 18, 2021)\n\n- Feature: Telepresence is now capable of forwarding the intercepted Pod's volume mounts (as Telepresence 0.x did) via the `--mount` flag to `telepresence intercept`.\n- Feature: Telepresence will now allow simultaneous intercepts in different namespaces.\n- Feature: It is now possible for a user to limit what namespaces that will be used by the DNS-resolver and the NAT.\n- Bugfix: Fix the kubectl version number check to handle version numbers with a \"+\" in them.\n- Bugfix: Fix a bug with some configurations on macOS where we clash with mDNSResponder's use of port 53.\n\n### 2.0.1 (February 9, 2021)\n\n- Feature: Telepresence is now capable of forwarding the environment variables of an intercepted service (as Telepresence 0.x did) and emit them to a file as text or JSON. The environment variables will also be propagated to any command started by doing a `telepresence intercept nnn -- <command>`.\n- Bugfix: A bug causing a failure in the Telepresence DNS resolver when attempting to listen to the Docker gateway IP was fixed. The fix affects Windows using a combination of Docker and WSL2 only.\n- Bugfix: Telepresence now works correctly while OpenVPN is running on macOS.\n- Change: The background processes `connector` and `daemon` will now use rotating logs and a common directory.\n  - macOS: `~/Library/Logs/telepresence/`\n  - Linux: `$XDG_CACHE_HOME/telepresence/logs/` or `$HOME/.cache/telepresence/logs/`\n"
  },
  {
    "path": "CHANGELOG.yml",
    "content": "# The YAML in this file should contain:\n#\n# items: An array of releases with the following attributes:\n#     - version: The (optional) version number of the release, if applicable.\n#     - date: >-\n#         The date of the release in the format YYYY-MM-DD.\n#         If the date is (SUPERSEDED), then the release didn't happen, which means\n#         that its notes belong to the next release.\n#     - notes: An array of noteworthy changes included in the release, each having the following attributes:\n#         - type: The type of change, one of `bugfix`, `feature`, `security` or `change`.\n#         - title: A short title of the noteworthy change.\n#         - body: >-\n#             Two or three sentences describing the change and why it\n#             is noteworthy.  This is HTML, not plain text or\n#             markdown.  It is handy to use YAML's \">-\" feature to\n#             allow line-wrapping.\n#         - image: >-\n#             The URL of an image that visually represents the\n#             noteworthy change.  This path is relative to the\n#             `release-notes` directory; if this file is\n#             `FOO/releaseNotes.yml`, then the image paths are\n#             relative to `FOO/release-notes/`.\n#         - docs: The path to the documentation page where additional information can be found.\n#\n# For older changes, see CHANGELOG.OLD.md\nitems:\n  - version: 2.27.2\n    date: 2026-03-09\n    notes:\n      - type: bugfix\n        title: Fix duplicate Section field in .deb package causing dpkg install failure\n        body: >-\n          The <code>.deb</code> package control file contained a duplicate <code>Section</code> field,\n          which caused <code>dpkg -i</code> to fail with a parsing error. The <code>Section</code>\n          field was specified both as a top-level <code>nfpm</code> default and explicitly under\n          <code>deb.fields</code>. Moved to the top-level <code>section</code> key and removed the\n          <code>deb.fields</code> block entirely.\n        docs: https://github.com/telepresenceio/telepresence/issues/4073\n  - version: 2.27.1\n    date: 2026-03-08\n    notes:\n      - type: bugfix\n        title: Fix duplicate Priority field in .deb package causing dpkg install failure\n        body: >-\n          The <code>.deb</code> package control file contained a duplicate <code>Priority</code> field,\n          which caused <code>dpkg -i</code> to fail with a parsing error. This was caused by\n          <code>nfpm</code> adding <code>Priority: optional</code> by default while the build\n          configuration also specified it explicitly under <code>deb.fields</code>.\n        docs: https://github.com/telepresenceio/telepresence/issues/4070\n      - type: bugfix\n        title: Fix ingest command lookup when container name is not specified\n        body: >-\n          The traffic manager now properly handles ingest lookups when using\n          <code>telepresence ingest &lt;workload&gt; -- command</code> without specifying\n          a container name. Previously, this would fail for workloads because the lookup\n          couldn't find ingests with empty container names. The traffic manager now provides\n          clearer error messages when a container name is required but not specified, and\n          correctly resolves ingests for single-container workloads automatically.\n        docs: https://github.com/telepresenceio/telepresence/issues/4067\n  - version: 2.27.0\n    date: 2026-02-28\n    notes:\n      - type: feature\n        title: Add macOS package installer with root daemon as a system service\n        body: >-\n          A new macOS package installer (.pkg) is now available that installs Telepresence with the root daemon\n          configured as a launchd service. This eliminates the need for elevated privileges when using Telepresence,\n          as the daemon starts automatically at boot and runs in the background. Available for both Intel (amd64)\n          and Apple Silicon (arm64) Macs.\n        docs: install/client\n      - type: feature\n        title: Add Linux package installers with root daemon as a system service\n        body: >-\n          New Linux package installers (.deb for Debian/Ubuntu and .rpm for Fedora/RHEL) are now available that\n          install Telepresence with the root daemon configured as a systemd service. This eliminates the need for\n          elevated privileges when using Telepresence, as the service is enabled and started automatically during\n          installation. Available for both amd64 and arm64 architectures.\n        docs: install/client\n      - type: feature\n        title: Add Windows installer with root daemon as a system service\n        body: >-\n          A new Windows installer (.exe) is now available that installs Telepresence with the root daemon configured\n          as a Windows service. The installer bundles WinFSP and SSHFS-Win dependencies for volume mount support,\n          adds Telepresence to the system PATH, and optionally installs the TelepresenceDaemon service. This eliminates\n          the need for elevated privileges when using Telepresence. Currently available for amd64 architecture only\n          due to dependency constraints.\n        docs: install/client\n      - type: feature\n        title: Add route-controller DaemonSet to prevent routing loops on local clusters\n        body: >-\n          A new optional <code>route-controller</code> DaemonSet can be deployed alongside the traffic-manager\n          on local Kubernetes clusters (Kind, minikube, k3d, Docker Desktop) to prevent routing loops caused by\n          deleted or non-existent service ClusterIPs. It installs an iptables <code>FORWARD</code> chain\n          <code>DROP</code> rule for the service CIDR on every node, and adds per-IP kernel blackhole routes\n          when a Service is deleted. Enable it with <code>routeController.enabled=true</code> in the Helm chart.\n        docs: reference/route-controller\n      - type: feature\n        title: Automatic cache cleanup on version change\n        body: >-\n          Telepresence now tracks its version in a version.json file in the cache directory. When the CLI detects\n          that the major.minor version differs from the running binary, it automatically quits running daemons and\n          clears stale cache entries (preserving logs). This prevents issues caused by leftover cache files from a\n          previous version. Patch and pre-release version changes do not trigger a cache cleanup.\n      - type: bugfix\n        title: Cluster DNS not injected into containers started by telepresence compose\n        body: >-\n          When using <code>telepresence compose up</code>, cluster hostnames did not resolve inside the compose \n          container because the daemon DNS IP and the tel2-search DNS search domain were not being added to the\n          generated compose spec. DNS and dns_search are now correctly set for all engaged compose services.\n        docs: https://github.com/telepresenceio/telepresence/issues/4053\n  - version: 2.26.2\n    date: 2026-02-14\n    notes:\n      - type: bugfix\n        title: Dial 127.0.0.1 instead of 0.0.0.0 when connecting to local daemons\n        body: >-\n          When a VPN routes all private network addresses, dialing 0.0.0.0 gets routed through the VPN instead\n          of reaching the locally running daemon. This causes Telepresence to report that no daemon is running\n          even though the processes are active and listening. The client now dials 127.0.0.1 explicitly, and\n          the Docker-published daemon port is bound to 127.0.0.1 to match.\n        docs: https://github.com/telepresenceio/telepresence/issues/4048\n      - type: bugfix\n        title: Fix HTTP intercepts via telepresence compose failing when httpFilters or httpPaths are set\n        body: >-\n          The compose extension set HeaderFilters and PathFilters on the intercept spec but left the mechanism as \"tcp\".\n          This caused the traffic manager to reject the intercept with \"global TCP/UDP intercepts are disabled\".\n          The mechanism is now correctly switched to \"http\" when any HTTP filters are specified, matching the behavior\n          of the CLI intercept command.\n      - type: bugfix\n        title: Fix \"root daemon is embedded\" error on Windows elevated terminals\n        body: >-\n          When running Telepresence in an elevated (administrator) terminal on Windows, commands like connect and\n          loglevel failed with \"root daemon is embedded\". The user daemon now correctly delegates to the in-process\n          root daemon session instead of returning an error.\n        docs: https://github.com/telepresenceio/telepresence/issues/4049\n      - type: bugfix\n        title: Fix h2c (HTTP/2 cleartext) prior knowledge not working on transport\n        body: >-\n          Since Go 1.24, having both HTTP1 and UnencryptedHTTP2 enabled on an HTTP transport causes Go to\n          default to HTTP/1.1 instead of using h2c prior knowledge. The transport now explicitly disables\n          HTTP1 when UnencryptedHTTP2 is enabled, fixing h2c communication for both the default forwarding\n          handler and intercepted traffic.\n        docs: https://github.com/telepresenceio/telepresence/issues/4056\n  - version: 2.26.1\n    date: 2026-01-26\n    notes:\n      - type: bugfix\n        title: Add support for \"warning\" as an alias for \"warn\" in log levels\n        body: >-\n          The \"warning\" alias for \"warn\" was the only acceptable value in the Helm chart, yet the traffic manager didn't accept it. This is\n          now fixed so that both names are accepted by the Helm chart and by the traffic manager.\n        docs: https://github.com/telepresenceio/telepresence/issues/4043\n  - version: 2.26.0\n    date: 2026-01-23\n    notes:\n      - type: feature\n        title: Add ability for cluster admins to revoke other users' intercepts.\n        body: |-\n          The traffic-manager can now execute commands defined in the traffic-manager ConfigMap. As a result, clients that are authorized to\n          update this ConfigMap can issue those commands.\n\n          This mechanism lays the groundwork for distinguishing administrators from regular users in Telepresence. Administrators can be\n          granted RBAC permissions that allow them to update the traffic-manager ConfigMap, while regular users cannot.\n\n          The new command, `telepresence revoke <intercept-id>`, uses this mechanism to revoke the intercept associated with the specified\n          ID.\n        docs: reference/engagements/conflicts\n      - type: feature\n        title: Add support for overriding intercepts owned by inactive clients\n        body: >-\n          Introduce the Helm chart setting `intercept.inactiveBlockTimeout` that controls the maximum amount of time an intercept may be\n          held by a client that is unreachable or inactive. Once this timeout is exceeded, the intercept no longer blocks conflicting\n          intercepts and may be automatically removed when another client attempts to create a conflicting intercept.\n        docs: reference/engagements/conflicts\n      - type: feature\n        title: Add support for sudo-rs\n        body: >-\n          Telepresence now supports running the root daemon using `sudo-rs`. This is necessary on Ubuntu where `sudo-rs` has become the\n          default for `sudo`.\n      - type: feature\n        title: Add configuration to disable global TCP/UDP intercepts\n        body: >-\n          A new Helm chart configuration option `intercept.allowGlobalIntercepts` has been added to control\n          whether global TCP/UDP intercepts are permitted. When set to `false`, only HTTP intercepts with\n          header or path filters are allowed, preventing users from creating global intercepts that block\n          other developers from intercepting the same port. This is particularly useful in shared development\n          environments where multiple developers need to work on the same service simultaneously. The setting\n          defaults to `true` to maintain full backward compatibility with existing deployments. When a user\n          attempts to create a global intercept while the setting is disabled, they receive a helpful error\n          message suggesting the use of `--http-header` or `--http-path-*` flags for HTTP-filtered intercepts.\n        docs: reference/cluster_config#restricting_global_intercepts\n      - type: feature\n        title: Enhanced Traffic Manager Startup Reliability\n        body: |-\n          The Traffic Manager deployment now includes a `startupProbe` that accurately detects when the service is fully initialized and\n          ready to handle traffic. This enhancement brings the following benefits:\n          \n          - **Prevents Premature Traffic Routing**: The manager only reports itself as ready after all configurations are loaded,\n          eliminating potential race conditions\n          - **Smoother Upgrades and Rollouts**: Deployment orchestration tools can reliably determine when the Traffic Manager is\n          operational, improving overall installation stability\n          \n          This change is particularly beneficial in large clusters or complex networking environments where initialization may take longer\n          than expected.\n      - type: feature\n        title: Support customizable daemon config file\n        body: >-\n          The config file for Telepresence is now configurable through the command-line flag `--config`.\n          The `--config <agent config>` flag of the `telepresence genyaml` command was renamed to `--agent` to avoid confusion.\n      - type: feature\n        title: Support customizable daemon log file paths\n        body: >-\n          Log file paths for the Telepresence daemons are now configurable through the command-line flag `--logfile`\n          that denotes a custom log file location or redirect of the log output to stdout/stderr. Two new log-level\n          configuration entries for `cli` and `kubeAuthDaemon` are also introduced, expanding the existing log-level\n          controls beyond just `userDaemon` and `rootDaemon`.\n      - type: feature\n        title: Add ability to exclude or include modifications made by other injectors when injecting the traffic agent.\n        body: >-\n          The Traffic Agent now has a new configuration option `agentInjector.mutationAware` that can be set to `false` to exclude\n          modifications made by other injectors when injecting the traffic agent. Setting `agentInjector.mutationAware=true` requires\n          `agentInjector.webhook.reinvocationPolicy=IfNeeded`. The default setting is `true`.\n      - type: feature\n        title: Add ability to disable the Traffic Agent's HTTP2/Clear-Text probing.\n        body: >-\n          The Traffic Agent now has a new configuration option `agent.enableH2cProbing` that can be set to `false` to disable the\n          HTTP2/Clear-Text probing. The default setting is `true` to preserve backwards compatibility, but this will be changed to `false`\n          in a future release.\n      - type: feature\n        title: Add ability to configure the Traffic Agent's retry interval for watching intercepts.\n        body: >-\n          The Traffic Agent's retry interval when it establishes its watcher for intercepts is now configurable using the Helm chart value\n          `agent.watchRetryInterval`. The default retry interval was also increased from 2 seconds to 10 seconds to improve resilience when\n          connections to the traffic manager are lost.\n      - type: feature\n        title: Make traffic-agent consumption metrics reporting optional.\n        body: >-\n          Metrics reporting for the traffic-agent can now be optionally enabled or disabled via the `agent.enableConsumptionMetrics` Helm\n          chart value. The traffic-agent metrics will also be completely disabled when the Helm chart value `prometheus.port` is unset or\n          zero.\n          This change is intended to reduce the amount of unnecessary traffic generated by the traffic-agent when consumption metrics\n          reporting is of no interest to the user.\n      - type: feature\n        title: Improved efficiency of traffic manager map updates.\n        body: >-\n          The watchable map has been refactored into a client/server model that supports delta-based updates. Where supported, gRPC now\n          transmits only incremental changes instead of full snapshots, significantly reducing payload sizes. This improvement is especially\n          important for large clusters, where full snapshots can be sizable and costly to transmit. Full snapshot streaming remains\n          available as a backward-compatible fallback for clients that do not support delta methods.\n      - type: change\n        title: Use TCP/IP instead of Unix sockets for all communication between local processes.\n        body: >-\n          Telepresence now uses TCP/IP sockets for all communication between local processes. This change reduces the\n          risk of socket-related errors caused by lingering sockets from previous Telepresence sessions. It also\n          eliminates the difficulties of using Unix sockets for communication between a system service and user\n          processes on Windows.\n      - type: change\n        title: Don't allow connect with --docker when client is configured with intercept.useFtp=true\n        body: |-\n          The `--docker` flag is not allowed when the client is configured with `intercept.useFtp=true` and an error is now generated\n          instantly by the `telepresence connect --docker` command.\n          \n          The docker volume plugin cannot use FTP because it requires two ports: a fixed control port that Telepresence can proxy, and a\n          dynamic data port (randomly chosen during connection) that Telepresence cannot proxy on-demand. The port-forwarder only forwards\n          pre-configured ports and doesn't understand FTP's protocol. Consequently, FTP isn't allowed in this scenario. If it was, then when\n          an FTP server tells the client to use an unpredictable second port for file transfers, Telepresence would block it—causing the\n          connection to fail every time.\n      - type: change\n        title: Better names for the Telepresence Daemons\n        body: |-\n          Using the name xxx-foreground isn't very intuitive when talking about daemon processes. Yes, the command, when issued in a\n          terminal, will start the daemon in foreground so the names of the commands does have some logic to them, but then again, starting\n          in the foreground is the default behavior of any command. And when the same command is started from the CLI, it will be started in\n          the background, despite its name.\n\n          The daemons are therefore now renamed:\n\n          - connector-foreground =&gt; userd\n          - daemon-foreground =&gt; rootd\n          - kubeauth-foreground =&gt; kubeauthd\n          \n          This also affects the Telepresence hidden CLI commands `telepresence daemon-foreground` and `telepresence connector-foreground`.\n      - type: bugfix\n        title: Add retry logic for tunnel connection attempts\n        body: >-\n          The tunnel dialer now uses a backoff-based retry mechanism when establishing connections. This ensures that dialing attempts for\n          both TCP and UDP protocols persist if the target IP is not immediately ready to receive requests.\n      - type: bugfix\n        title: Retry mechanism for client tunnel creation\n        body: >-\n          The traffic agent now includes a backoff-based retry mechanism when establishing tunnel streams to a client. This prevents\n          \"no dial watcher\" connection failures caused by a race condition where the tunnel request arrives before the client has fully\n          initialized its communication channel.\n      - type: bugfix\n        title: Fix \"close of closed channel\" panic in the root daemon process.\n        body: >-\n          The root daemon process would sometimes panic with \"close of closed channel\" due to a race condition in the DNS cache logic.\n          This issue has been fixed.\n  - version: 2.25.2\n    date: 2025-12-26\n    notes:\n      - type: bugfix\n        title: Ensure that the exit code from a docker command becomes the exit code of the Telepresence command.\n        body: >-\n          When running a Docker command using `telepresence docker-run` or `telepresence curl`, the exit code would be 1 for all non-zero\n          exit codes from the Docker command. This has been fixed so that the exit code from the Docker command becomes the exit code of the\n          Telepresence command.\n      - type: bugfix\n        title: Fix a bug causing truncation of command text when generating external command help.\n        body: >-\n          The Telepresence CLI would truncate the command text when generating help for external commands such as `docker compose` that\n          had text spanning more than one line. This has been fixed so that the full command text is displayed.\n      - type: bugfix\n        title: Fix schema for agent.image.pullSecrets\n        body: >-\n          The `agent.image.pullSecrets` is referenced by the helm chart's deployment.yaml but was previously disallowed by the schema file.\n  - version: 2.25.1\n    date: 2025-11-10\n    notes:\n      - type: bugfix\n        title: Volumes did not mount correctly when using `telepresence connect --docker` when Docker had IPv6 enabled.\n        body: >-\n          Telepresence failed to mount volumes after connecting with `telepresence connect --docker` when Docker Engine had IPv6 enabled in\n          its default bridge network. Disabling IPv6 in the Telepresence client configuration did not resolve the issue. This was fixed in\n          Telepresence Volume Plugin \"telemount\" version 0.3.2, which circumvented a [bug in sshfs](https://github.com/libfuse/sshfs/issues/335).\n          Additionally, the volume plugin will no longer use IPv6 when the client configuration `docker.enableIPv6` is set to `false`.\n      - type: bugfix\n        title: Remove unnecessary setcap from traffic binary\n        body: >-\n          The setcap capability (cap_net_bind_service) was removed from the traffic binary build process.\n          This capability was originally added to allow the binary to bind to privileged ports, specifically\n          port 443 for the mutating webhook. Since version 2.24.0, the default mutating webhook port was\n          changed to 8443 (a non-privileged port), making this capability unnecessary. Removing it simplifies\n          the build process and reduces the security surface area.\n  - version: 2.25.0\n    date: 2025-10-16\n    notes:\n      - type: feature\n        title: HTTP Intercepts with HTTP header and path filtering\n        body: |-\n          Telepresence now supports HTTP Intercepts, enabling fine-grained HTTP traffic filtering for intercepts.\n          Users can intercept only specific HTTP requests based on headers and URL paths using the new `--http-header`,\n          `--http-path-prefix`, `--http-path-equal`, and `--http-path-regex` flags. This allows multiple developers\n          to work on the same service simultaneously by intercepting only their specific traffic patterns, rather than\n          intercepting all traffic to a service.\n\n          **Routing Precedence Model**: Header-based intercepts take priority over path-only intercepts. When multiple\n          intercepts are active on the same workload, requests are evaluated against header-based filters first, then\n          path-only filters. This enables different developers to use header-based personal intercepts (e.g.,\n          `x-user=alice`) while others use path-based intercepts (e.g., `/admin/*`) without conflicts.\n\n          **Conflict Detection**: Intercepts conflict only when their filters would route the same traffic to different\n          destinations. Key rules:\n\n          - Different header values (e.g., `X-User=adam` vs `X-User=bertil`) do NOT conflict\n          - Header filters use subset logic: `X-User=adam` conflicts with `X-User=adam, X-Session=123` (first is subset)\n          - Same headers with different paths do NOT conflict: `X-User=adam + /api/*` vs `X-User=adam + /admin/*`\n          - Path-only intercepts operate at a lower priority tier than header-based intercepts\n\n          The feature maintains full backward compatibility with existing TCP intercepts.\n        docs: howtos/engage#intercept-your-application\n      - type: feature\n        title: TLS/mTLS Intercept Support\n        body: |-\n          Support was added for HTTP-filtered intercepts on applications using TLS/mTLS encryption. The new functionality enables\n          Telepresence to decrypt and inspect encrypted traffic by accessing TLS certificates, facilitating debugging and testing of secure\n          applications.\n\n          Certificates can be accessed via mounted volumes or Kubernetes secrets in the same namespace. New annotations\n          (`telepresence.io/downstream-cert-path` and `telepresence.io/downstream-cert-secret`) allow configuration of certificate paths or\n          secrets for decrypting traffic on specified ports. For mTLS, the `telepresence.io/upstream-cert-` annotation prefix supports\n          re-encryption of upstream traffic using client-side certificates.\n\n          For self-signed certificates, the `telepresence.io/upstream-insecure-skip-verify` annotation bypasses verification, enabling\n          HTTP-filtered intercepts in development environments. The `--plaintext` option allows unencrypted traffic during intercepts or\n          wiretaps.\n        docs: howtos/mtls\n      - type: feature\n        title: Add MCP server to Telepresence CLI\n        body: >-\n          The Telepresence CLI now includes a lightweight MCP server that can be used to allow local AI agents to execute\n          some CLI commands, such as connecting to a traffic manager, listing interceptable apps, and creating an intercept.\n          The server can be enabled using the new `telepresence mcp claude enable` or `telepresence mcp vscode enable` commands.\n      - type: feature\n        title: Enhance Resilience of Engagements During Traffic-Manager Redeploys\n        body: >-\n          The telepresence client and traffic-agent now automatically reconnect to the traffic-manager after a restart.\n          Upon reconnection, they share their current state, ensuring ongoing engagements remain uninterrupted. This\n          improvement minimizes user impact during traffic-manager upgrades.\n      - type: feature\n        title: Add support for IPv6 and dual-stack when using `telepresence connect --docker`\n        body: |-\n          Telepresence now supports using `telepresence connect --docker` together with Kubernetes single-stack IPv4\n          networking, single-stack IPv6 networking, or dual-stack networking with both network families. Both are\n          enabled by default, but can be disabled by setting the `client.docker.enableIPv4` or `client.docker.enableIPv6`\n          to false in the Helm chart, or by using the corresponding settings `docker.enableIPv4` or `docker.enableIPv6`\n          the client configuration file.\n\n          The new dual-stack support requires the teleroute network plugin 0.4.0 or later. The client will install this\n          version automatically unless you work in an air-gapped environment.\n      - type: feature\n        title: RESTful API Service Reintroduced with HTTP Filtering Support\n        body: >-\n          The Telepresence RESTful API service has been restored with enhanced support for HTTP header and path filtering. This service\n          enables workloads to programmatically query whether they should handle requests based on active intercepts.\n          Added `--metadata` flag allows attaching custom metadata to intercepts that can be retrieved through the API endpoints.\n          The API server is now accessible via `TELEPRESENCE_API_HOST` and `TELEPRESENCE_API_PORT` environment variables in both cluster\n          pods and local intercept handlers.\n        docs: reference/restapi\n      - type: feature\n        title: More efficient DNS handling in the traffic-manager\n        body: |-\n          Telepresence will no longer send DNS queries for A and AAAA records to the traffic-manager. Instead, it will\n          send a single query for the name and then derive the record type from the type of IP address (IPv4 or IPv6) in\n          the response. This reduces the number of DNS queries sent to the cluster's DNS server and makes the behavior\n          more consistent with the `net.LookupNetIP` function in the Go standard library, which the traffic-manager\n          ultimately uses.\n\n          The lookups will now be performed exclusively by the traffic-manager, never by the traffic-agent. This means\n          that traffic-agents with special DNS configurations might stop working. If this is a problem, the old behavior\n          can be restored by setting the `client.dns.useComplexLookup` parameter in the Helm chart or the\n          `dns.useComplexLookup` parameter in the client configuration file.\n      - type: feature\n        title: Updated Helm chart to include keywords and the source repository URL.\n        body: >-\n          Improves the Helm chart's discoverability on platforms like Artifact Hub and\n          automatically adds a direct link to the source code for users, providing better context.\n      - type: change\n        title: Telepresence client now requires a traffic manager version of at least 2.21.0.\n        body: >-\n          The traffic manager is now required to be at least version 2.21.0. Versions earlier than 2.21.0 will no\n          longer work. The reason for this is that implementing the new reconnect behavior would require too much\n          conditional code with older traffic-managers, and a lot of functionality wouldn't work anyway.\n      - type: change\n        title: Build binaries and docker images that are stripped from dwarf and debug info.\n        body: >-\n          The Telepresence binaries and docker images are now built with the `-w -s` flags, which strip the debug\n          symbols and the DWARF information. This reduces the size of the binaries and docker images by about 50MB.\n          Debug binaries can be built using `DEBUG=1 make build`.\n  - version: 2.24.1\n    date: 2025-09-05\n    notes:\n      - type: bugfix\n        title: Fix invalid filename generated by telepresence gather-logs command\n        body: >-\n          The `telepresence gather-logs` command would generate a filename that was invalid on Windows unless the user\n          specified the filename explicitly using the `--output-file` flag. This was fixed using a more condensed\n          format for the timestamp in the filename.\n        docs: https://github.com/telepresenceio/telepresence/issues/3956\n      - type: bugfix\n        title: A `telepresence connect --docker` fails kubeconfig points to a port-forwarded localhost\n        body: >-\n          Telepresence would fail to connect to a cluster from within a container if the kubeconfig pointed to a\n          port-forwarded localhost because that localhost is not reachable from within the container. This situation is\n          now detected so that the address used from within the container has \"localhost\" replaced with\n          \"host.docker.internal\", or an alternative alias configured by the user using the new `docker.hostGateway`\n          parameter in the client configuration.\n      - type: bugfix\n        title: A `telepresence connect --docker` would fail with some k3s configurations\n        body: >-\n          Telepresence would fail to connect to a k3s cluster unless the cluster's IP was a loopback address. This is\n          now changed so that any IP:port combination is accepted as long as a container can be found that defines a\n          mapping for it.\n      - type: bugfix\n        title: Restore default value for agent-state.yaml in the traffic-manager configmap\n        body: >-\n          The value was previously an empty string which caused problems when when Argo CD tried to synchronize it.\n        docs: https://github.com/telepresenceio/telepresence/pull/3953\n  - version: 2.24.0\n    date: 2025-08-25\n    notes:\n      - type: feature\n        title: Support for Docker Compose\n        body: >-\n          Telepresence now supports integration with Docker Compose. It connects to and interacts with cluster resources\n          by utilizing `x-tele` extensions within a Docker Compose specification. These extensions configure\n          your local services to effectively act as handlers for Telepresence connections, providing them with the\n          necessary access to the traffic, volumes, and environment of the engaged container.\n        docs: howtos/docker-compose\n      - type: feature\n        title: Serve up a web-page with telepresence serve.\n        body: >-\n          A new `telepresence serve <service>` command was added that starts a web browser on the specified service. The\n          command is especially useful when used in combination with `telepresence connect --docker` because it will\n          then expose the given service on a random port on localhost.\n        docs: reference/cli/telepresence_serve\n      - type: feature\n        title: Add ability to optionally clean up sidecars that have been idle above a specified duration\n        body: >-\n          Added configuration parameter `agent.maxIdleTime` to the Helm Chart, to control how long a sidecar can be idle before it is cleaned up.\n          The updating of latestEngagementTime is done every 1m in the Remain Call, which is now called every 1min, compared to every 5s in the past.\n          The agent state (containing latestEngagementTime) is updated in memory, and lazily persisted to the traffic-manager configmap every 2min.\n          Removal of the sidecar is also done in the Remain Call, if the sidecar has been idle for longer than the configured `agent.maxIdleTime`.\n      - type: feature\n        title: Add option to drop client label in prometheus metrics for GDPR compliance\n        docs: https://github.com/telepresenceio/telepresence/issues/3491\n        body: >-\n          The Helm Chart now has a `prometheus.dropClientLabel` option that can be set to true to drop the client label from the prometheus metrics.\n          This is useful for GDPR compliance, as the client label contains personal data, which can be potentially problematic, i.e allowing the ability to\n          track the working times of an individual.\n      - type: feature\n        title: Prefix metrics with \"telepresence_\"\n        docs: https://github.com/telepresenceio/telepresence/issues/3920\n        body: Avoids metric conflicts and makes these more explicit to improve search in observability stacks.\n      - type: feature\n        title: CLI documentation in markdown format\n        body: >-\n          The Telepresence CLI is now capable of generating its own documentation in markdown format using the\n          new `telepresence man-pages` command. The generated documentation is included under the heading \"Telepresence\n          CLI\" in the the Telepresence reference documentation.\n        docs: reference/cli/telepresence\n      - type: feature\n        title: Service Port Rerouting\n        body: >-\n          The telepresence connect command introduces a new `--reroute-remote <host>:<port>:<new-port>[/{tcp|udp}]` flag,\n          allowing users to remap service ports. This feature redirects requests sent to `<host>:<new-port>` to\n          `<host>:<port>` within the Telepresence VIF. The flag can be repeated.\n      - type: feature\n        title: Local Port Rerouting\n        body: >-\n          The telepresence connect command introduces a new `--reroute-local <local-port>:<host>:<port>[/{tcp|udp}]` flag,\n          allowing users to redirect requests sent to ports on localhost to arbitrary service ports. This feature enables\n          requests sent to `localhost:<local-port>` to be redirected to `<host:port>`. The flag can be repeated.\n      - type: feature\n        title: Add information about using Kubernetes auth plugins when using Telepresence CLI in a container\n        body: >-\n          Kubernetes, and hence the Telepresence CLI, must have access to auth plugins declared in the kubeconfig. A\n          section was added to the documentation explaining how to achieve this when using the Telepresence CLI in a\n          container.\n        docs: reference/inside-container#kubernetes-auth-plugins\n      - type: feature\n        title: Add log directory to the output of `telepresence config view`\n        body: >-\n          The `telepresence config view` command now includes the path to the directory where the Telepresence logs are\n          stored.\n      - type: change\n        title: The default port for the mutating webhook is now 8443. It used to be 443\n        body: >-\n          Port numbers below 1000 are reserved for privileged processes and are often restricted by firewalls.\n          Consequently, the default port for the mutating webhook was changed from 443 to 8443. You can override this\n          default port using the agentInjector.webhook.port value in the Helm Chart. This change is particularly\n          significant for clusters using Telepresence, where firewall rules limit the admission webhook's access to\n          worker nodes, such as in an Amazon EKS cluster.\n  - version: 2.23.6\n    date: 2025-07-23\n    notes:\n      - type: bugfix\n        title: Public DNS names aren't resolved from local docker application started by Telepresence\n        body: >-\n          A container running using `telepresence docker-run` or `telepresence <engage-type> --docker-run` was not able to resolve public\n          DNS names such as \"google.com\". The problem is caused by an undocumented behavior in Docker's internal DNS-resolver when the\n          `--dns` flag is used in conjunction with the teleroute network, where the DNS-server no longer finds public names even thought\n          another bridge network is connected. The problem was solved by using a fallback resolver in the Telepresence DNS resolver.\n  - version: 2.23.5\n    date: 2025-07-20\n    notes:\n      - type: bugfix\n        title: Let docker.Start pass on --interactive to docker start.\n        body: >-\n          An `-i` or `--interactive` flag given when the user runs a container with telepresence must be propagated to\n          `docker start` to attach `stdin`.\n  - version: 2.23.4\n    date: 2025-07-18\n    notes:\n      - type: bugfix\n        title: Never truncate meaningful output from a command\n        body: >-\n          The new human-friendly output using a progress reporter would sometimes truncate error output. This is no\n          longer the case. Instead, all output will be wrapped.\n      - type: bugfix\n        title: Typo in client mount-policy \"RemoteReadonly\" should be \"RemoteReadOnly\"\n        body: >-\n          The Helm Chart correctly expects the remote read-only mount policy to be `RemoteReadOnly`, but the client\n          expected it to be `RemoteReadonly` (without a leading capital letter in the word \"only\").\n      - type: bugfix\n        title: DNS server does not respect semicolons as comments in resolv.conf files\n        body: >-\n          Telepresence does not work correctly if `/etc/resolv.conf` contains semicolons, which are valid comments as of\n          [linux manpage](https://man7.org/linux/man-pages/man5/resolv.conf.5.html).\n        docs: https://github.com/telepresenceio/telepresence/issues/3908\n      - type: bugfix\n        title: Use NXDOMAIN instead SERVFAIL for DNS recursion errors (timeouts)\n        body: >-\n          A DNS for a single label name that fails in a minikube - or another type of local cluster - will sometimes\n          result in a recursive lookup on the host. Without any type of recursion detection, this lookup will timeout\n          waiting for itself. Previously, this resulted in a `SERVFAIL` from the cluster DNS, which triggered renewed\n          lookup attempts that never stopped. This is now changed so that the same type of timeouts instead results\n          in an `NXDOMAIN` error that doesn't trigger renewed attempts.\n\n          Also, the recursion check now handles that the cluster's DNS adds suffixes from its search-path.\n        docs: reference/config#recursioncheck\n  - version: 2.23.3\n    date: 2025-07-07\n    notes:\n      - type: bugfix\n        title: Fix tunnel channel reuse in traffic-agent to prevent connection failures\n        body: >-\n          Previously, when a tunnel became active, the map maintained by the traffic-agent containing channel values for\n          agent-to-client tunnels wasn't cleared. This resulted in closed channels remaining in the map. When port\n          numbers were eventually reused, these stale entries would be discovered and their closed channels would cause\n          immediate stream termination, leading to data loss.\n      - type: bugfix\n        title: The -p flags would have no effect in combination with --docker-run\n        body: |-\n          When using `telepresence <engage> --docker-run` with a `-p <port spec>` flag, the Docker driver silently\n          ignored the port specification. This occurred because the `--network=<teleroute network>` flag disabled both\n          additional network directives and the default bridge network (which is normally used when no network is\n          specified). This has been resolved by:\n\n          1. Adding the teleroute network after container creation instead of using a flag\n          2. Replacing the single `docker run` command with a sequence of:\n             - `docker create`\n             - Network addition\n             - `docker start`\n      - type: bugfix\n        title: Requests lost when using wiretap\n        body: >-\n          Wiretap connection `Close` and `Write` could sometimes be out of sync, so that the `Close` would be executed\n          before the `Write`, causing a \"read/write on closed pipe\" error and loss of data.\n  - version: 2.23.2\n    date: 2025-06-27\n    notes:\n      - type: bugfix\n        title: Adding an alsoProxy subnet with 32-bit mask no longer works on macOS\n        body: >-\n          Routing improvements introduced in 2.23.0 surfaced a problem when using special handling of submets with\n          32-bit masks. The special handling now causes problems on macOS. The logic is now conditioned to only run\n          under linux since it's no longer needed on other operating systems.\n      - type: bugfix\n        title: The gather-logs command produces no cluster-side logs when connected with --docker\n        body: >-\n          The `telepresence gather-logs` command did not include cluster-side logs when connected using `--docker`,\n          because it tried to store such logs in a temporary directory created by the CLI. The directory was not mounted\n          in the daemon container. This is now changed so that the temporary directory is created under the users\n          cache directory, which is guaranteed to be mounted on the container.\n      - type: bugfix\n        title: Docker volume mounts failing when connected using both --docker --proxy-via flags\n        body: >-\n          The volume mounter would fail when doing a `telepresence connect --docker --proxy-via all=<workload>` followed\n          by an intercept using `--docker-run`, because the bridge that the daemon created would try to access the\n          intercepted pod using its proxied IP. Now, the bridge will instead use the pod's real IP.\n  - version: 2.23.1\n    date: 2025-06-24\n    notes:\n      - type: feature\n        title: New telepresence helm version command.\n        body: >-\n          The new `telepresence helm version` command prints the version of the helm client that is embedded in the\n          telepresence binary.\n      - type: bugfix\n        title: Engagement disconnects after certain amount of time\n        body: |-\n          The configuration parameter `connectionTTL`, controlling how long a client could be completely idle before\n          the traffic-manager or traffic-agent would consider it dead and disconnect (default 24 hours), had no effect.\n          Instead, an engagement would disconnect after 2 hours (the default gRPC `keepAlive.Time` duration). The\n          default of 24 hours is now reinstated.\n\n          The Helm value `client.connectionTTL` was moved to `grpc.connectionTTL` because it is a server configuration.\n          The old value will still work, but it is deprecated and will be removed eventually.\n        docs: https://github.com/telepresenceio/telepresence/issues/3861\n      - type: bugfix\n        title: Telepresence breaks if config.yml exists but is empty\n        body: >-\n          Telepresence would refuse to connect with a misleading error if the `config.yml` file containing the client's\n          configuration parameters existed but was empty.\n        docs: https://github.com/telepresenceio/telepresence/issues/3887\n  - version: 2.23.0\n    date: 2025-06-17\n    notes:\n      - type: feature\n        title: New telepresence wiretap command\n        body: >-\n          The new `telepresence wiretap` command introduces a read-only form of an `intercept` where the original\n          container will run unaffected while a copy of the wiretapped traffic is sent to the client.\n\n          Similar to an `ingest`, a `wiretap` will always enforce read-only status on all volume mounts, and since that\n          makes the `wiretap` completely read-only, there's no limit to how many simultaneous wiretaps that can be\n          served. In fact, a `wiretap` and an `intercept` on the same port can run simultaneously.\n      - type: feature\n        title: Add Telepresence Docker Network Plugin \"Teleroute\"\n        body: |-\n          The new Teleroute plugin makes it possible for containers to use the Telepresence daemon's VIF without having\n          to change their network mode, i.e. a `--network container:<daemon container>` is no longer needed. Instead,\n          a container can use a custom network created when the Telepresence daemon connects to the cluster.\n          This network uses the new driver \"teleroute\" which is provided by Telepresence.\n\n          With the Teleroute Docker network plugin in place, there's no longer a need for special handling of network\n          related docker flags, and the following changes have been made:\n\n          1. The Teleroute Docker network driver will be installed unless it is already present.\n          2. A Teleroute network will be created when starting the Telepresence daemon as a container. This network will\n             then communicate with that container and expose the same CIDRs as the daemon's VIF.\n          3. A container started with `telepresence curl`, or\n             `telepresence {ingest|intercept|replace|wiretap} --docker-{run|build|debug}` will no longer change its\n             network mode using `--network container:<daemon container>`, instead it will use\n             `--network <name of teleroute network>`.\n          4. As a consequence of #3, published ports and other networks that are added no longer need special handling\n             using socat containers, so all of that has been removed.\n        docs: reference/teleroute\n      - type: feature\n        title: Control whether the initContainer injection is enabled/disabled\n        body: >-\n          The initContainer injection can be optionally disabled by setting the `agent.initContainer.enabled` parameter\n          to false in the `values.yaml` file of the Helm chart. This feature was added to improve compatibility with\n          systems like OpenShift where the initContainer injection cannot be used due to inability to give initContainer\n          NET_ADMIN permissions.\n      - type: feature\n        title: Human friendly progress reporting\n        body: >-\n          Telepresence now uses a progress reporter that is very similar to the one used by Docker compose. The\n          implementation is a variation of that reporter's source code, so big thanks to the Docker compose CLI authors\n          for making it available as OSS.\n\n          A new global `--progress <progress>` flag was added. It defaults to \"auto\" which means that the style is\n          chosen depending on whether the command runs from a tty type terminal. Other possible values are \"plain\",\n          \"quiet\", and \"json\". `--progress quiet` is implied when formatted output is chosen using `--output json|yaml`.\n      - type: feature\n        title: Add the ability to use a name for the target host, and defer its resolution\n        body: >-\n          Knowing the IP of the local service that acts as the handler service for an intercept, replace, or wiretap\n          is not possible until that service has been started, and telepresence will therefore now accept a name for the\n          `--address` flag. The name is not resolved by the daemon until a request is made to the engaged container on a\n          port that is routed to the local service.\n      - type: feature\n        title: Add intercept.mountsRoot to the client configuration\n        body: >-\n          The new `intercept.mountsRoot` can be set to a directory that will be used as the root for all automatically\n          generated mount directories. The default is to use the platforms temp directory.\n\n          The setting is not used on windows, where the mounts use drive letters.\n      - type: feature\n        title: Add docker.addHostGateway to the client configuration.\n        body: >-\n          When `docker.addHostGateway` is set to `true`, the `docker run`\n          that starts the containerized Telepresence daemon will include the flag\n          `--add-host host.docker.internal:host-gateway`.\n\n          The flag is set to `true` by default on linux platforms and `false` on\n          other platforms.\n      - type: feature\n        title: Client configuration to override the Helm download URL\n        body: >-\n          The default download URL `oci://ghcr.io/telepresenceio/telepresence-oss` used when installing Helm charts with\n          versions that differ from the version of the embedded Helm chart can now be overridden using the client config\n          value `helm.chartURL`.\n      - type: change\n        title: Dropped support for Telepresence legacy flags\n        body: |-\n          The `telepresence` CLI command will no longer support legacy flags such as:\n\n          - `--swap-deployment`\n          - `--new-deployment`\n          - `--docker-mount`\n          - `--method`\n\n          A \"Legacy Telepresence command used\" warning has been printed for several years now, and the mapping for the\n          `--swap-deployment` was the `intercept` command, which is very confusing today since we now have the `replace`\n          command.\n      - type: bugfix\n        title: Let containerized daemon consistently use the same port for gRPC\n        body: >-\n          The port used for the containerized gRPC was randomly selected using the hosts network namespace. This is now\n          changed so that the port used by the container is preset and configurable and then mapped to a random port\n          on the host.\n\n          The port number can be configured using `grpc.daemonPort` and defaults to `4038`.\n      - type: bugfix\n        title: Telepresence fails to start the root daemon on Windows unless current user is the administrator\n        body:\n          The telepresence CLI starts a user daemon and a root daemon. The latter is started using administrator\n          privileges. On a Windows box, this means that the root daemon runs using a different user account (typically\n          \"Administrator\") unless the current user can run processes with elevated privileges. The socket used for\n          communication with the root daemon was assumed to reside in `%USERPROFILE%\\AppData\\Local\\telepresence`\n          and was therefore not found by the CLI and the user daemon. The location will henceforth always be based on the\n          `%USERDATA` of the CLI user.\n        docs: https://github.com/telepresenceio/telepresence/issues/3875\n      - type: bugfix\n        title: Telepresence DNS Fallback stripping CNAME information from DNS Records.\n        body: >-\n          The fallback DNS server used on Linux systems without a systemd.resolved configuration, would assume that\n          suffixes belonging to the `search` defined in the `/etc/resolved` had been added by the caller. Since this\n          search path was assumed to be intended for the local machine only, the suffix was stripped off prior to\n          sending the name to the cluster for resolution. This made queries fail that relied on the qualified name to\n          resolve CNAME records. The logic stripping the suffix was therefore removed.\n        docs: https://github.com/telepresenceio/telepresence/issues/3873\n  - version: 2.22.6\n    date: 2025-06-03\n    notes:\n      - type: bugfix\n        title: Regression causing \"unexpected slice size\" with older traffic-managers.\n        body: >-\n          Older traffic-managers have a different way of reporting the service-subnet. The new way, using a\n          list of subnets reused a proto slice in the GRPC message that was expected to be empty, but older\n          traffic-managers will pass the IP of the kube-dns here. It cannot be parsed as a list of\n          subnets. A check that remedies this mismatch was inserted.\n  - version: 2.22.5\n    date: 2025-05-29\n    notes:\n      - type: bugfix\n        title: Unable to correctly determine service CIDR with Kubernetes >= 1.33\n        body: >-\n          Starting with Kubernetes 1.33, the strategy of extracting the cluster's service CIDR from an error message no\n          longer works because the error message has changed. The root cause for this is that Kubernetes introduced the\n          ability to use [Multiple Service CIDRs](https://gist.github.com/aojea/c20eb117bf1c1214f8bba26c495be9c7). Since\n          `ServiceCIDR` is now a resource, it can be easily retrieved (and modified) using standard Kubernetes client\n          API calls, and this is what the traffic manager will use going forward.\n\n          The fix required an addition to the traffic-manager's RBAC, granting it sufficient permissions to list\n          `networking.k8s.io/servicecidrs`. A future enhancement will allow the traffic manager to watch for service\n          CIDR changes.\n      - type: bugfix\n        title: Helm chart schema type for nodeSelector was incorrect\n        body: >-\n          The Helm chart schema for the `nodeSelector` value was incorrect. Kubernetes defines different types for\n          nodeSelector (inside PodSpec objects) and NodeSelector (inside NodeAffinity, VolumeNodeAffinity and a\n          bunch of other places). The schema was changed to use the correct type. The name `nodeSelector` is still\n          used in the Helm chart so this change is backwards compatible.\n      - type: bugfix\n        title: Pods with container ports named the same caused intercept to fail\n        body: >-\n          Intercept container ports now have numbers appended to them if there are multiple\n          ports from multiple containers with the same name. This bugfix works around an issue\n          where Kubernetes allows multiple port definitions in a pod spec to have the same name.\n      - type: bugfix\n        title: Don't include k8s-defs.json to chart package\n        body: >-\n          The k8s-defs.json was unnecessarily included to the Helm chart package and this increased the Helm release\n          secret size so much that it could prevent installation of the Helm chart depending on k8s settings. To fix\n          this k8s-defs.json is not included to the Helm chart anymore.\n  - version: 2.22.4\n    date: 2025-04-26\n    notes:\n      - type: bugfix\n        title: Don't require internet access when installing the traffic-manager using Helm\n        body: >-\n          A regression occurred with the introduction of Helm validation in version 2.22.0. The schema relied on\n          external HTTPS links, which inadvertently created a requirement for internet accessibility. To resolve this,\n          we have embedded these resources within the schema, thus removing the need for an internet connection.\n      - type: bugfix\n        title: Client failed connect with \"failed to exit idle mode\" in the connector.log after being idle\n        body: >-\n          The port-forward connections used for connecting the daemon to the traffic-agents were using\n          an incorrect context, causing them to fail after being idle for some time.\n      - type: bugfix\n        title: Fix deadlock in Telepresence daemon\n        body: >-\n          A deadlock would sometimes occur in the Telepresence daemon that prevented it from doing a clean\n          exit during `telepresence quit`.\n      - type: bugfix\n        title: Don't log error message when a pod watcher ends due to cancellation\n        body: >-\n          Errors printed in the daemon log during normal cancellation of the WatchAgentPods goroutine are\n          now removed.\n  - version: 2.22.3\n    date: 2025-04-08\n    notes:\n      - type: change\n        title: The Windows install script will now install Telepresence to \"%ProgramFiles%\\telepresence\"\n        body: |-\n          Telepresence is now installed into \"%ProgramFiles%\\telepresence\" instead of \"C:\\telepresence\".\n          The directory and the Path entry for  `C:\\telepresence` are not longer used and should be removed.\n      - type: bugfix\n        title: The Windows install script didn't handle upgrades properly\n        body: |-\n          The following changes were made:\n\n            - The script now requires administrator privileges\n            - The Path environment is only updated when there's a need for it\n        docs: https://github.com/telepresenceio/telepresence/issues/3827\n      - type: bugfix\n        title: The Telepresence Helm chart could not be used as a dependency in another chart.\n        body: >-\n          The JSON schema validation implemented in Telepresence 2.22.0 had a defect: it rejected the `global` object.\n          This object, a Helm-managed construct, facilitates the propagation of arbitrary configurations from a parent\n          chart to its dependencies. Consequently, charts intended for dependency use must permit the presence of the\n          `global` object.\n        docs: https://github.com/telepresenceio/telepresence/issues/3833\n      - type: bugfix\n        title: Recreating namespaces was not possible when using a dynamically namespaced Traffic Manager\n        body: >-\n          A shared informer was sometimes reused when namespaces were removed and then later added again, leading\n          to errors like \"handler ... was not added to shared informer because it has stopped already\".\n        docs: https://github.com/telepresenceio/telepresence/issues/3831\n      - type: bugfix\n        title: Single label name DNS lookups didn't work unless at least one traffic-agent was installed\n        body: >-\n          A problem with incorrect handling of single label names in the traffic-manager's DNS resolver was fixed. The\n          problem would cause lookups like `curl echo` to fail, even though telepresence was connected to a namespace\n          containing an \"echo\" service, unless at least one of the workloads in the connected namespace had a\n          traffic-agent.\n  - version: 2.22.2\n    date: 2025-03-28\n    notes:\n      - type: bugfix\n        title: Panic when using telepresence replace in a IPv6-only cluster\n        body: |-\n          A \"slice bounds out of range\" would occur when the targeted Pod's Traffic Agent requested a local dialer to\n          be created on the client. This was due to a glitch in the VPN-tunnel implementation that got triggered when\n          a remote IPv6-address was combined with a local IPv4-address.\n        docs: https://github.com/telepresenceio/telepresence/issues/3828\n  - version: 2.22.1\n    date: 2025-03-27\n    notes:\n      - type: bugfix\n        title: Only restore inactive traffic-agent after a replace.\n        body: |-\n          A regression in the 2.20.0 release would cause the traffic-agent to be replaced with a dormant version that\n          didn't touch any ports when an intercept ended. This terminated other ongoing intercepts on the same pod.\n          This is now changed so that the traffic-agent remains unaffected for this use-case.\n  - version: 2.22.0\n    date: 2025-03-14\n    notes:\n      - type: feature\n        title: New telepresence replace command.\n        body: |-\n          The new `telepresence replace` command simplifies and clarifies container replacement.\n\n          Previously, the `--replace` flag within the `telepresence intercept` command was used to replace containers.\n          However, this approach introduced inconsistencies and limitations:\n\n          * **Confusion:** Using a flag to modify the core function of a command designed for traffic interception led\n            to ambiguity.\n          * **Inaccurate Behavior:** Replacement was not possible when no incoming traffic was intercepted, as the\n            command's design focused on traffic routing.\n\n          To address these issues, the `--replace` flag within `telepresence intercept` has been deprecated. The new\n          `telepresence replace` command provides a dedicated and consistent method for replacing containers, enhancing\n          clarity and reliability.\n\n          Key differences between `replace` and `intercept`:\n\n          1. **Scope:** The `replace` command targets and affects an entire container, impacting all its traffic, while\n             an `intercept` targets specific services and/or service/container ports.\n          2. **Port Declarations:** Remote ports specified using the `--port` flag are container ports.\n          3. **No Default Port:** A `replace` can occur without intercepting any ports.\n          4. **Container State:** During a `replace`, the original container is no longer active within the cluster.\n\n          The deprecated `--replace` flag still works, but is hidden from the `telepresence intercept` command help, and\n          will print a deprecation warning when used.\n      - type: feature\n        title: Add json-schema for the Telepresence Helm Chart\n        body: >-\n          Helm can validate a chart using a json-schema using the command `helm lint`, and this schema can be part of\n          the actual Helm chart. The telepresence-oss Helm chart now includes such a schema, and a new\n          `telepresence helm lint` command was added so that linting can be performed using the embedded chart.\n      - type: feature\n        title: No dormant container present during replace.\n        body: |-\n          Telepresence will no longer inject a dormant container during a `telepresence replace` operation. Instead, the\n          Traffic Agent now directly serves as the replacement container, eliminating the need to forward traffic to the\n          original application container. This simplification offers several advantages when using the `--replace` flag:\n\n            - **Removal of the init-container:** The need for a separate init-container is no longer necessary.\n            - **Elimination of port renames:** Port renames within the intercepted pod are no longer required.\n      - type: feature\n        title: One single invocation of the Telepresence intercept command can now intercept multiple ports.\n        body: >-\n          It is now possible to intercept multiple ports with one single invocation of `telepresence intercept` by just\n          repeating the `--port` flag.\n      - type: feature\n        title: Unify how Traffic Manager selects namespaces\n        body: |-\n          The definition of what namespaces that a Traffic Manager would manage use was scattered into several Helm\n          chart values, such as `manager.Rbac.namespaces`, `client.Rbac.namespaces`, and\n          `agentInjector.webhook.namespaceSelector`. The definition is now unified to the mutual exclusive top-level\n          Helm chart values `namespaces` and `namespaceSelector`.\n\n          The `namespaces` value is just for convenience and a short form of expressing:\n          ```yaml\n          namespaceSelector:\n            matchExpressions:\n             - key: kubernetes.io/metadata.name\n               operator: in\n               values: <namespaces>.\n          ```\n\n        docs: install/manager#static-versus-dynamic-namespace-selection\n      - type: feature\n        title: Improved control over how remote volumes are mounted using mount policies\n        body: >-\n          Mount policies, that affects how the telepresence traffic-agent shares the pod's volumes, and also how the\n          client will mount them, can now be provided using the Helm chart value `agent.mountPolicies` or as JSON\n          object in the workload annotation `telepresence.io/mount-policies`. A mount policy is applied to a volume\n          or to all paths matching a path-prefix (distinguished by checking if first character is a '/'), and can\n          be one of `Ignore`, `Local`, `Remote`, or `RemoteReadOnly`.\n      - type: feature\n        title: List output includes workload kind.\n        body: >-\n          The output of the `telepresence list` command will now include the workload kind (deployment, replicaset,\n          statefulset, or rollout) in all entries.\n      - type: feature\n        title: Add ability to override the default securityContext for the Telepresence init-container\n        body: >-\n          Users can now use the Helm value `agent.initSecurityContext` to override the default securityContext for the\n          Telepresence init-container.\n      - type: change\n        title: Let download page use direct links to GitHub\n        body: >-\n          The download links on the release page now points directly to the assets on the download page, instead of\n          using being routed from getambassador.io/download/tel2oss/releases.\n      - type: change\n        title: Use telepresence.io as annotation prefix instead of telepresence.getambassador.io\n        body: >-\n          The workload and pod annotations used by Telepresence will now use the prefix `telepresence.io` instead of\n          `telepresence.getambassador.io`. The new prefix is consistent with the prefix used by labels, and it also\n          matches the host name of the documentation site. Annotations using the old name will still work, but warnings\n          will be logged when they are encountered.\n      - type: change\n        title: Make the DNS recursion check configurable and turn it off by default.\n        body: >-\n          Very few systems experience a DNS recursion lookup problem. It can only occur when the cluster runs locally\n          and the cluster's DNS is configured to somehow use DNS server that is started by Telepresence. The check\n          is therefore now configurable through the client setting `dns.recursionCheck`, and it is `false` by default.\n      - type: change\n        title: Trigger the mutating webhook with Kubernetes eviction objects instead of patching workloads.\n        body: >-\n          Telepresence will now attempt to evict pods in order to trigger the traffic-agent's injection or removal, and\n          revert to patching workloads if evictions are prevented by the pod's disruption budget. This causes a slight\n          change in the traffic-manager RBAC, as the traffic-manager must be able to create \"pod/eviction\" objects.\n      - type: change\n        title: The telepresence-agents configmap is no longer used.\n        body: >-\n          The traffic-agent configuration was moved into a pod-annotation. This avoids sync problems between the\n          telepresence-agents (which is no no longer present) and the pods.\n      - type: change\n        title: Drop deprecated current-cluster-id command.\n        body: >-\n          The clusterID was deprecated some time ago, and replaced by the ID of the namespace where the traffic-manager\n          is installed.\n      - type: bugfix\n        title: Make telepresence connect --docker work with Rancher Desktop\n        body: >-\n          Rancher Desktop will start a K3s control-plane and typically expose the\n          Kubernetes API server at `127.0.0.1:6443`. Telepresence can connect to\n          this cluster when running on the host, but the address is not available\n          when connecting in docker mode.\n\n          The problem is solved by ensuring that the Kubernetes API server address used when\n          doing a `telepresence connect --docker` is swapped from 127.0.0.1 to the\n          internal address of the control-plane node. This works because that\n          address is available to other docker containers, and the Kubernetes API\n          server is configured with a certificate that accepts it.\n      - type: bugfix\n        title: Rename charts/telepresence to charts/telepresence-oss.\n        body: >-\n          The Helm chart name \"telepresence-oss\" was inconsistent with its contained folder \"telepresence\". As a result,\n          attempts to install the chart using an argo ApplicationSet failed. The contained folder was renamed to match\n          the chart name.\n      - type: bugfix\n        title: Conflict detection between namespaced and cluster-wide install.\n        body: >-\n          The namespace conflict detection mechanism would only discover conflicts between two _namespaced_ Traffic\n          Managers trying to manage the same namespace. This is now fixed so that all types conflicts are discovered.\n        docs: install/manager#namespace-collision-detection\n      - type: bugfix\n        title: Don't dispatch DNS discovery queries to the cluster.\n        body: >-\n          macOS based systems will often PTR queries using nameslike `b._dns-sd._udp`, lb._dns-sd._udp`, or\n          `db-dns-sd._udp`. Those queries are no longer dispatched to the cluster.\n      - type: bugfix\n        title: Using the --namespace option with telepresence causes a deadlock.\n        body: >-\n          Using `telepresence list --namespace <ns>` with a namespace different from the one that telepresence was\n          connected to, would cause a deadlock, and then produce an empty list.\n      - type: bugfix\n        title: Fix problem with exclude-suffix being hidden by DNS search path.\n        body: >-\n          In some situations, a name ending with an exclude-suffix like \"xyz.com\" would be expanded by a search path\n          into \"xyz.com.&lt;connected namespace&gt;\" and therefore not be excluded. Instead, the name was sent to the cluster\n          to be resolved, causing an unnecessary load on its DNS server.\n  - version: 2.21.3\n    date: 2025-02-06\n    notes:\n      - type: bugfix\n        title: Using the --proxy-via flag would sometimes cause connection timeouts.\n        body: >-\n          Typically, a `telepresence connect --proxy-via <subnet>=<workflow>` would fail with a \"deadline exceeded\"\n          message when several workloads were present in the namespace, the one targeted by the proxy-via didn't yet\n          have an agent installed, and other workloads had an agent. This was due to a race condition in the logic\n          for the agent-based port-forwards in the root daemon. The conditions causing this race are now eliminated.\n      - type: bugfix\n        title: Fix panic in root daemon when using the \"allow conflicting subnets\" feature on macOS.\n        body: >-\n          A regression was introduced in version 2.21.0, causing a panic due to an unimplemented method in the\n          TUN-device on macOS based clients.\n      - type: bugfix\n        title: Ensure that annotation enabled traffic-agents are uninstall when uninstalling the traffic-manager.\n        body: >-\n          A traffic-agent injected because the workload had the inject annotation enabled would sometimes not get\n          uninstalled when the traffic-manager was uninstalled.\n  - version: 2.21.2\n    date: 2025-01-26\n    notes:\n      - type: bugfix\n        title: Fix panic when agentpf.client creates a Tunnel\n        body: >-\n          A race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored\n          when creating its port-forward to the agent. The implementation could handle one such requests but not\n          several, resulting in a panic in situations where multiple simultaneous requests were made to the same client\n          during a very short time period,\n      - type: bugfix\n        title: Fix goroutine leak in dialer.\n        body: >-\n          The context passed to the `Tunnel` call that creates a stream for a dialer, was not cancelled when the dialer\n          was finished, so the stream was never properly closed, leading to one dormant goroutine for each stream.\n  - version: 2.21.1\n    date: 2024-12-17\n    notes:\n      - type: bugfix\n        title: Allow ingest of serverless deployments without specifying an inject-container-ports annotation\n        body: >-\n          The ability to intercept a workload without a service is built around the\n          `telepresence.getambassador.io/inject-container-ports` annotation, and it was also required in order to ingest\n          such a workload. This was counterintuitive and the requirement was removed. An ingest doesn't use a port.\n        docs: https://github.com/telepresenceio/telepresence/issues/3741\n      - type: bugfix\n        title: Upgrade module dependencies to get rid of critical vulnerability.\n        body: >-\n          Upgrade module dependencies to latest available stable. This includes upgrading golang.org/x/crypto, which\n          had critical issues, from 0.30.0 to 0.31.0 where those issues are resolved.\n  - version: 2.21.0\n    date: 2024-12-13\n    notes:\n      - type: feature\n        title: Automatic VPN conflict avoidance\n        body: >-\n          Telepresence not only detects subnet conflicts between the cluster and workstation VPNs but also resolves them\n          by performing network address translation to move conflicting subnets out of the way.\n        docs: reference/vpn\n      - type: feature\n        title: Virtual Address Translation (VNAT).\n        body: >-\n          It is now possible to use a virtual subnet without routing the affected IPs to a specific workload. A new\n          `telepresence connect --vnat CIDR` flag was added that will perform virtual network address translation of\n          cluster IPs. This flag is very similar to the `--proxy-via CIDR=WORKLOAD` introduced in 2.19, but without\n          the need to specify a workload.\n        docs: reference/vpn\n      - type: feature\n        title: Intercepts targeting a specific container\n        body: >-\n          In certain scenarios, the container owning the intercepted port differs\n          from the container the intercept targets. This port owner's sole purpose\n          is to route traffic from the service to the intended container, often\n          using a direct localhost connection.\n\n          This update introduces a `--container <name>` option to the intercept\n          command. While this option doesn't influence the port selection, it\n          guarantees that the environment variables and mounts propagated to the\n          client originate from the specified container. Additionally, if the\n          `--replace` option is used, it ensures that this container is replaced.\n        docs: reference/engagements/container\n      - type: feature\n        title: New telepresence ingest command\n        body: >-\n          The new `telepresence ingest` command, similar to `telepresence intercept`, provides local access to the\n          volume mounts and environment variables of a targeted container. However, unlike `telepresence intercept`,\n          `telepresence ingest` does not redirect traffic to the container and ensures that the mounted volumes are\n          read-only.\n\n          An ingest requires a traffic-agent to be installed in the pods of the targeted workload. Beyond that, it's\n          a client-side operation. This allows developers to have multiple simultaneous ingests on the same container.\n        docs: howtos/intercepts#ingest-your-service\n      - type: feature\n        title: New telepresence curl command\n        body: >-\n          The new `telepresence curl` command runs curl from within a container. The command requires that a connection\n          has been established using `telepresence connect --docker`, and the container that runs `curl` will share the\n          same network as the containerized telepresence daemon.\n        docs: reference/docker-run#the-telepresence-curl-command\n      - type: feature\n        title: New telepresence docker-run command\n        body: >-\n          The new `telepresence docker-run <flags and arguments>` requires that a connection has been established using\n          `telepresence connect --docker` It will perform a `docker run <flags and arguments>` and add the flag necessary\n          to ensure that started container shares the same network as the containerized telepresence daemon.\n        docs: reference/docker-run#the-telepresence-docker-run-command\n      - type: feature\n        title: Mount everything read-only during intercept\n        body: >-\n          It is now possible to append \":ro\" to the intercept `--mount` flag value. This ensures that all remote volumes\n          that the intercept mounts are read-only.\n      - type: feature\n        title: Unify client configuration\n        body: >-\n          Previously, client configuration was divided between the config.yml file and a Kubernetes extension. DNS and\n          routing settings were initially found only in the extension. However, the Helm client structure allowed\n          entries from both.\n\n          To simplify this, we've now aligned the config.yml and Kubernetes extension with the Helm client structure.\n          This means DNS and routing settings are now included in both. The Kubernetes extension takes precedence over\n          the config.yml and Helm client object.\n\n          While the old-style Kubernetes extension is still supported for compatibility, it cannot be used with the new\n          style.\n        docs: reference/config\n      - type: feature\n        title: Use WebSockets for port-forward instead of the now deprecated SPDY.\n        body: >-\n          Telepresence will now use WebSockets instead of SPDY when creating port-forwards to the Kubernetes Cluster, and\n          will fall back to SPDY when connecting to clusters that don't support SPDY. Use of the deprecated SPDY can be\n          forced by setting `cluster.forceSPDY=true` in the `config.yml`.\n\n          See [Streaming Transitions from SPDY to WebSockets](https://kubernetes.io/blog/2024/08/20/websockets-transition/)\n          for more information about this transition.\n      - type: feature\n        title: Make usage data collection configurable using an extension point, and default to no-ops\n        body: >-\n          The OSS code-base will no longer report usage data to the proprietary collector at Ambassador Labs. The actual calls\n          to the collector remain, but will be no-ops unless a proper collector client is installed using an extension point.\n      - type: feature\n        title: Add deployments, statefulSets, replicaSets to workloads Helm chart value\n        body: >-\n          The Helm chart value `workloads` now supports the kinds `deployments.enabled`, `statefulSets.enabled`, `replicaSets.enabled`.\n          and `rollouts.enabled`. All except `rollouts` are enabled by default. The traffic-manager will ignore workloads, and\n          Telepresence will not be able to intercept them, if the `enabled` of the corresponding kind is set to `false`.\n        docs: reference/engagements/sidecar#disable-workloads\n      - type: feature\n        title: Improved command auto-completion\n        body: >-\n          The auto-completion of namespaces, services, and containers have been added where appropriate, and the default\n          file auto completion has been removed from most commands.\n      - type: feature\n        title: Docker run flags --publish, --expose, and --network now work with docker mode connections\n        body: >-\n          After establishing a connection to a cluster using `telepresence connect --docker`, you can run new containers that share\n          the same network as the containerized daemon that maintains the connection. This enables seamless communication between\n          your local development environment and the remote services.\n\n          Normally, Docker has a limitation that prevents combining a shared network configuration with custom networks\n          and exposing ports. However, Telepresence now elegantly circumvents this limitation so that a container started with\n          `telepresence docker-run`, `telepresence intercept --docker-run`, or `telepresence ingest --docker-run` can use flags\n          like `--network`, `--publish`, or `--expose`.\n\n          To achieve this, Telepresence temporarily adds the necessary network to the containerized daemon. This allows the new\n          container to join the same network. Additionally, Telepresence starts extra socat containers to handle port mapping,\n          ensuring that the desired ports are exposed to the local environment.\n        docs: reference/docker-run#the-telepresence-docker-run-command\n      - type: feature\n        title: Prevent recursion in the Telepresence Virtual Network Interface (VIF)\n        body: >-\n          Network problems may arise when running Kubernetes locally (e.g., Docker Desktop, Kind, Minikube, k3s),\n          because the VIF on the host is also accessible from the cluster's nodes. A request that isn't handled by a\n          cluster resource might be routed back into the VIF and cause a recursion.\n\n          These recursions can now be prevented by setting the client configuration property\n          `routing.recursionBlockDuration` so that new connection attempts are temporarily blocked for a specific\n          IP:PORT pair immediately after an initial attempt, thereby effectively ending the recursion.\n        docs: howtos/cluster-in-vm\n      - type: feature\n        title: Allow Helm chart to be included as a sub-chart\n        body: >-\n          The Helm chart previously had the unnecessary restriction that the .Release.Name under which telepresence is installed is literally\n          called \"traffic-manager\".  This restriction was preventing telepresence from being included as a sub-chart in a parent chart\n          called anything but \"traffic-manager\". This restriction has been lifted.\n      - type: feature\n        title: Add Windows arm64 client build\n        body: >-\n          Telepresence client is now available for Windows ARM64.\n          Updated the release workflow files in github actions to build and publish the Windows ARM64 client.\n      - type: change\n        title: The --agents flag to telepresence uninstall is now the default.\n        body: >-\n          The `telepresence uninstall` was once capable of uninstalling the traffic-manager as well as traffic-agents.\n          This behavior has been deprecated for some time now and in this release, the command is all about uninstalling\n          the agents. Therefore the `--agents` flag was made redundant and whatever arguments that are given to the\n          command must be name of workloads that have an agent installed unless the `--all-agents` is used, in which\n          case no arguments are allowed.\n      - type: change\n        title: Performance improvement for the telepresence list command\n        body: >-\n          The `telepresence list` command will now retrieve its data from the traffic-manager, which significantly\n          improves its performance when used on namespaces that have a lot of workloads.\n      - type: change\n        title: During an intercept, the local port defaults to the targeted port of the intercepted container instead of 8080.\n        body: >-\n          Telepresence mimics the environment of a target container during an intercept, so it's only natural that the default\n          for the local port is determined by the targeted container port rather than just defaulting to 8080.\n\n          A default can still be explicitly defined using the `config.intercept.defaultPort` setting.\n      - type: change\n        title: Move the telepresence-intercept-env configmap data into traffic-manager configmap.\n        body: >-\n          There's no need for two configmaps that store configuration data for the traffic manager. The traffic-manager\n          configmap is also watched, so consolidating the configuration there saves some k8s API calls.\n      - type: change\n        title: Tracing was removed.\n        body: >-\n          The ability to collect trace has been removed along with the `telepresence gather-traces` and\n          `telepresence upload-traces` commands. The underlying code was complex and has not been well maintained since\n          its inception in 2022. We have received no feedback on it and seen no indication that it has ever been used.\n      - type: bugfix\n        title: Remove obsolete code checking the Docker Bridge for DNS\n        body: >-\n          The DNS resolver checked the Docker bridge for messages on Linux. This code was obsolete and caused problems\n          when running in Codespaces.\n      - type: bugfix\n        title: Fix telepresence connect confusion caused by /.dockerenv file\n        body: >-\n          A `/.dockerenv` will be present when running in a GitHub Codespaces environment. That doesn't mean that\n          telepresence cannot use docker, or that the root daemon shouldn't start.\n      - type: bugfix\n        title: Cap timeouts.connectivityCheck at 5 seconds.\n        body: >-\n          The timeout value of `timeouts.connectivityCheck` is used when checking if a cluster is already reachable\n          without Telepresence setting up an additional network route. If it is, this timeout should be high enough to\n          cover the delay when establishing a connection. If this delay is higher than a second, then chances are very\n          low that the cluster already is reachable, and if it can, that all accesses to it will be very slow. In such\n          cases, Telepresence will create its own network interface and do perform its own tunneling.\n\n          The default timeout for the check remains at 500 millisecond, which is more than sufficient for the majority\n          of cases.\n      - type: bugfix\n        title: Prevent that traffic-manager injects a traffic-agent into itself.\n        body: >-\n          The traffic-manager can never be a subject for an intercept, ingest, or proxy-via, because that means that\n          it injects the traffic-agent into itself, and it is not designed to do that. A user attempting this will\n          now see a meaningful error message.\n      - type: bugfix\n        title: Don't include pods in the kube-system namespace when computing pod-subnets from pod IPs\n        body: >-\n          A user would normally never access pods in the `kube-system` namespace directly, and automatically including pods\n          included there when computing the subnets will often lead to problems when running the cluster locally. This namespace\n          is therefore now excluded in situations when the pod subnets are computed from the IPs of pods. Services in this\n          namespace will still be available through the service subnet.\n\n          If a user should require the pod-subnet to be mapped, it can be added to the `client.routing.alsoProxy`\n          list in the helm chart.\n      - type: bugfix\n        title: Let routes belonging to an allowed conflict be added as a static route on Linux.\n        body: >-\n          The `allowConflicting` setting didn't always work on Linux because the conflicting subnet was just added as a\n          link to the TUN device, and therefore didn't get subjected to routing rule used to assign priority to the\n          given subnet.\n  - version: 2.20.3\n    date: 2024-11-18\n    notes:\n      - type: bugfix\n        title: Ensure that Telepresence works with GitHub Codespaces\n        body: >-\n          GitHub Codespaces runs in a container, but not as root. Telepresence didn't handle this situation\n          correctly and only started the user daemon. The root daemon was never started.\n        docs: https://github.com/telepresenceio/telepresence/issues/3722\n      - type: bugfix\n        title: Mounts not working correctly when connected with --proxy-via\n        body: >-\n          A mount would try to connect to the sftp/ftp server using the original (cluster side) IP although that\n          IP was translated into a virtual IP when using `--proxy-via`.\n        docs: https://github.com/telepresenceio/telepresence/issues/3715\n  - version: 2.20.2\n    date: 2024-10-21\n    notes:\n      - type: bugfix\n        title: Crash in traffic-manager configured with agentInjector.enabled=false\n        body: >-\n          A traffic-manager that was installed with the Helm value `agentInjector.enabled=false` crashed when a\n          client used the commands `telepresence version` or `telepresence status`. Those commands would call\n          a method on the traffic-manager that panicked if no traffic-agent was present. This method will now\n          instead return the standard `Unavailable` error code, which is expected by the caller.\n  - version: 2.20.1\n    date: 2024-10-10\n    notes:\n      - type: bugfix\n        title: Some workloads missing in the telepresence list output (typically replicasets owned by rollouts).\n        body: >-\n          Version 2.20.0 introduced a regression in the `telepresence list` command, resulting in the omission of\n          all workloads that were owned by another workload. The correct behavior is to just omit those workloads\n          that are owned by the supported workload kinds `Deployment`, `ReplicaSet`, `StatefulSet`, and `Rollout`.\n          Furthermore, the `Rollout` kind must only be considered supported when the Argo Rollouts feature is\n          enabled in the traffic-manager.\n      - type: bugfix\n        title: Allow comma separated list of daemons for the gather-logs command.\n        body: >-\n          The name of the `telepresence gather-logs` flag `--daemons` suggests that the argument can contain more\n          than one daemon, but prior to this fix, it couldn't. It is now possible to use a comma separated\n          list, e.g. `telepresence gather-logs --daemons root,user`.\n  - version: 2.20.0\n    date: 2024-10-03\n    notes:\n      - type: feature\n        title: Add timestamp to telepresence_logs.zip filename.\n        body: >-\n          Telepresence is now capable of easily find telepresence gather-logs by certain timestamp.\n      - type: feature\n        title: Enable intercepts of workloads that have no service.\n        body: >-\n          Telepresence is now capable of intercepting workloads that have no associated service. The intercept will then target container port\n          instead of a service port. The new behavior is enabled by adding a <code>telepresence.getambassador.io/inject-container-ports</code>\n          annotation where the value is a comma separated list of port identifiers consisting of either the name or the port number of a container\n          port, optionally suffixed with `/TCP` or `/UDP`.\n        docs: https://telepresence.io/docs/reference/engagements/cli#intercepting-without-a-service\n      - type: feature\n        title: Publish the OSS version of the telepresence Helm chart\n        body: >-\n          The OSS version of the telepresence helm chart is now available at ghcr.io/telepresenceio/telepresence-oss, and\n          can be installed using the command:<br/>\n          <code>helm install traffic-manager oci://ghcr.io/telepresenceio/telepresence-oss --namespace ambassador --version 2.20.0</code>\n          The chart documentation is published at <a href=\"https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss\">ArtifactHUB</a>.\n        docs: https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss\n      - type: feature\n        title: Control the syntax of the environment file created with the intercept flag --env-file\n        body: >-\n          A new <code>--env-syntax &lt;syntax&gt;</code> was introduced to allow control over the syntax of the file created when using the intercept\n          flag <code>--env-file &lt;file&gt;</code>. Valid syntaxes are &quot;docker&quot;, &quot;compose&quot;, &quot;sh&quot;, &quot;csh&quot;, &quot;cmd&quot;,\n          and &quot;ps&quot;; where &quot;sh&quot;, &quot;csh&quot;, and &quot;ps&quot; can be suffixed with &quot;:export&quot;.\n        docs: https://telepresence.io/docs/reference/environment\n      - type: feature\n        title: Add support for Argo Rollout workloads.\n        body: >-\n          Telepresence now has an opt-in support for Argo Rollout workloads.\n          The behavior is controlled by `workloads.argoRollouts.enabled` Helm chart value.\n          It is recommended to set the following annotation <code>telepresence.getambassador.io/inject-traffic-agent: enabled</code>\n          to avoid creation of unwanted revisions.\n      - type: bugfix\n        title: Enable intercepts of containers that bind to podIP\n        body: >-\n          In previous versions, the traffic-agent would route traffic to localhost during periods when an intercept wasn't\n          active. This made it impossible for an application to bind to the pod's IP, and it also meant that service meshes binding\n          to the podIP would get bypassed, both during and after an intercept had been made.\n          This is now changed, so that the traffic-agent instead forwards non intercepted requests to the pod's IP, thereby\n          enabling the application to either bind to localhost or to that IP.\n      - type: change\n        title: Use ghcr.io/telepresenceio instead of docker.io/datawire for OSS images and the telemount Docker volume plugin.\n        body: >-\n          All OSS telepresence images and the telemount Docker plugin are now published at the public registry ghcr.io/telepresenceio\n          and all references from the client and traffic-manager has been updated to use this registry\n          instead of the one at docker.io/datawire.\n      - title: Use nftables instead of iptables-legacy\n        type: change\n        body: >-\n          Some time ago, we introduced iptables-legacy because users had problems using Telepresence with Fly.io where nftables\n          wasn't supported by the kernel. Fly.io has since fixed this, so Telepresence will now use nftables again. This in turn,\n          ensures that modern systems that lack support iptables-legacy will work.\n      - type: bugfix\n        title: Root daemon wouldn't start when sudo timeout was zero.\n        body: >-\n          The root daemon refused to start when <code>sudo</code> was configured with a <code>timestamp_timeout=0</code>.\n          This was due to logic that first requested root privileges using a sudo call, and then relied on that these\n          privileges were cached, so that a subsequent call using <code>--non-interactive</code> was guaranteed to succeed.\n          This logic will now instead do one single sudo call, and rely solely on sudo to print an informative prompt and\n          start the daemon in the background.\n      - type: bugfix\n        title: Detect minikube network when connecting with --docker\n        body: >-\n          A <code>telepresence connect --docker</code> failed when attempting to connect to a minikube that\n          uses a docker driver because the containerized daemon did not have access to the <code>minikube</code>\n          docker network. Telepresence will now detect an attempt to connect to that network and attach it to\n          the daemon container as needed.\n  - version: 2.19.1\n    date: \"2024-07-12\"\n    notes:\n      - type: feature\n        title: Add brew support for the OSS version of Telepresence.\n        body: >-\n          The Open-Source Software version of Telepresence can now be installed using the brew formula\n          via <code>brew install telepresenceio/telepresence/telepresence-oss</code>.\n        docs: https://github.com/telepresenceio/telepresence/issues/3609\n      - type: feature\n        title: Add --create-namespace flag to the telepresence helm install command.\n        body: >-\n          A <code>--create-namespace</code> (default <code>true</code>) flag was added to the <code>telepresence helm\n          install</code> command. No attempt will be made to create a namespace for the traffic-manager if it is explicitly\n          set to <code>false</code>. The command will then fail if the namespace is missing.\n      - type: feature\n        title: Introduce DNS fallback on Windows.\n        body: >-\n          A <code>network.defaultDNSWithFallback</code> config option has been introduced on Windows. It will cause the\n          DNS-resolver to fall back to the resolver that was first in the list prior to when Telepresence establishes a\n          connection. The option is default <code>true</code> since it is believed to give the best experience but can be\n          set to <code>false</code> to restore the old behavior.\n      - type: feature\n        title: Brew now supports MacOS (amd64/arm64) / Linux (amd64)\n        body: >-\n          The brew formula can now dynamically support MacOS (amd64/arm64) / Linux (amd64) in a single formula\n        docs: https://github.com/datawire/homebrew-blackbird/issues/19\n      - type: feature\n        title: Add ability to provide an externally-provisioned webhook secret\n        body: >-\n          Added <code>supplied</code> as a new option for <code>agentInjector.certificate.method</code>.\n          This fully disables the generation of the Mutating Webhook's secret, allowing the chart to use the values of a\n          pre-existing secret named <code>agentInjector.secret.name</code>. Previously, the install would fail when it\n          attempted to create or update the externally-managed secret.\n      - type: feature\n        title: Let PTR query for DNS server return the cluster domain.\n        body: >-\n          The <code>nslookup</code> program on Windows uses a PTR query to retrieve its displayed \"Server\" property.\n          This Telepresence DNS resolver will now return the cluster domain on such a query.\n      - type: feature\n        title: Add scheduler name to PODs templates.\n        body: >-\n          A new Helm chart value <code>schedulerName</code> has been added. With this feature, we are\n          able to define some particular schedulers from Kubernetes to apply some different strategies to allocate telepresence resources,\n          including the Traffic Manager and hooks pods.\n      - type: bugfix\n        title: Race in traffic-agent injector when using inject annotation\n        body: >-\n          Applying multiple deployments that used the <code>telepresence.getambassador.io/inject-traffic-agent: enabled</code> would cause\n          a race condition, resulting in a large number of created pods that eventually had to be deleted, or sometimes in\n          pods that didn't contain a traffic agent.\n      - type: bugfix\n        title: Fix configuring custom agent security context\n        body: ->\n          The traffic-manager helm chart will now correctly use a custom agent security context if one is provided.\n  - version: 2.19.0\n    date: \"2024-06-15\"\n    notes:\n      - type: feature\n        title: Warn when an Open Source Client connects to an Enterprise Traffic Manager.\n        body: >-\n          The difference between the OSS and the Enterprise offering is not well understood, and OSS users often install\n          a traffic-manager using the Helm chart published at getambassador.io. This Helm chart installs an enterprise\n          traffic-manager, which is probably not what the user would expect. Telepresence will now warn when an OSS client\n          connects to an enterprise traffic-manager and suggest switching to an enterprise client, or use\n          <code>telepresence helm install</code> to install an OSS traffic-manager.\n      - type: feature\n        title: Add scheduler name to PODs templates.\n        body: >-\n          A new Helm chart value <code>schedulerName</code> has been added. With this feature, we are\n          able to define some particular schedulers from Kubernetes to apply some different strategies to allocate telepresence resources,\n          including the Traffic Manager and hooks pods.\n      - type: bugfix\n        title: Improve traffic-manager performance in very large clusters.\n        body: ->\n          The traffic-manager will now use a shared-informer when keeping track of deployments. This will significantly\n          reduce the load on the Kublet in large clusters and therefore lessen the risk for the traffic-manager being\n          throttled, which can lead to other problems.\n      - type: bugfix\n        title: Kubeconfig exec authentication failure when connecting with --docker from a WSL linux host\n        body: >-\n          Clusters like Amazon EKS often use a special authentication binary that is declared in the kubeconfig using an\n          <code>exec</code> authentication strategy. This binary is normally not available inside a container. Consequently,\n          a modified kubeconfig is used when <code>telepresence connect --docker</code> executes, appointing a <code>kubeauth\n          </code> binary which instead retrieves the authentication from a port on the Docker host that communicates with another\n          process outside of Docker. This process then executes the original <code>exec</code> command to retrieve the necessary\n          credentials.\n\n          This setup was problematic when using WSL, because even though <code>telepresence connect --docker</code> was executed on\n          a Linux host, the Docker host available from <code>host.docker.internal</code> that the <code>kubeauth</code> connected to\n          was the Windows host running Docker Desktop. The fix for this was to use the local IP of the default route instead of\n          <code>host.docker.internal</code> when running under WSL..\n  - version: 2.18.6\n    date: (SUPERSEDED)\n    notes:\n      - type: bugfix\n        title: Fix bug in workload cache, causing endless recursion when a workload uses the same name as its owner.\n        body: >-\n          The workload cache was keyed by name and namespace, but not by kind, so a workload named the same as its\n          owner workload would be found using the same key. This led to the workload finding itself when looking up\n          its owner, which in turn resulted in an endless recursion when searching for the topmost owner.\n      - type: bugfix\n        title: FailedScheduling events mentioning node availability considered fatal when waiting for agent to arrive.\n        body: >-\n          The traffic-manager considers some events as fatal when waiting for a traffic-agent to arrive after an injection\n          has been initiated. This logic would trigger on events like &quot;Warning FailedScheduling 0/63 nodes are\n          available&quot; although those events indicate a recoverable condition and kill the wait. This is now fixed so\n          that the events are logged but the wait continues.\n  - version: 2.18.5\n    date: (SUPERSEDED)\n    notes:\n      - type: bugfix\n        title: Improve how the traffic-manager resolves DNS when no agent is installed.\n        body: >-\n          The traffic-manager is typically installed into a namespace different from the one that clients are\n          connected to. It's therefore important that the traffic-manager adds the client's namespace when\n          resolving single label names in situations where there are any agents to dispatch the DNS query to.\n      - type: change\n        title: Removal of ability import legacy artifact into Helm.\n        body: >-\n          A helm install would make attempts to find manually installed artifacts and make them managed by\n          Helm by adding the necessary labels and annotations. This was important when the Helm chart was first\n          introduced but is far less so today, and this legacy import was therefore removed.\n      - type: bugfix\n        title: Docker aliases deprecation caused failure to detect Kind cluster.\n        body: >-\n          The logic for detecting if a cluster is a local Kind cluster, and therefore needs some special attention when\n          using <code>telepresence connect --docker</code>, relied on the presence of <code>Aliases</code> in the Docker\n          network that a Kind cluster sets up. In Docker versions from 26 and up, this value is no longer used, but the\n          corresponding info can instead be found in the new <code>DNSNames</code> field.\n        docs: https://docs.docker.com/engine/deprecated/#container-short-id-in-network-aliases-field\n      - type: bugfix\n        title: Include svc as a top-level domain in the DNS resolver.\n        body: >-\n          It's not uncommon that use-cases involving Kafka or other middleware use FQNs that end with\n          &quot;svc&quot;. The core-DNS resolver in Kubernetes can resolve such names. With this bugfix,\n          the Telepresence DNS resolver will also be able to resolve them, and thereby remove the need\n          to add &quot;.svc&quot; to the include-suffix list.\n        docs: https://github.com/telepresenceio/telepresence/issues/2814\n      - type: feature\n        title: Add ability to enable/disable the mutating webhook.\n        body: >-\n          A new Helm chart boolean value <code>agentInjector.enable</code> has been added that controls the agent-injector\n          service and its associated mutating webhook. If set to <code>false</code>, the service, the webhook, and the\n          secrets and certificates associated with it, will no longer be installed.\n      - type: feature\n        title: Add ability to mount a webhook secret.\n        body: >-\n          A new Helm chart value <code>agentInjector.certificate.accessMethod</code> which can be set to <code>watch</code>\n          (the default) or <code>mount</code> has been added. The <code>mount</code> setting is intended for clusters with\n          policies that prevent containers from doing a <code>get</code>, <code>list</code> or <code>watch</code> of a\n          <code>Secret</code>, but where a latency of up to 90 seconds is acceptable between the time the secret is\n          regenerated and the agent-injector picks it up.\n      - type: feature\n        title: Make it possible to specify ignored volume mounts using path prefix.\n        body: >-\n          Volume mounts like <code>/var/run/secrets/kubernetes.io</code> are not declared in the workload. Instead, they\n          are injected during pod-creation and their names are generated. It is now possible to ignore such mounts using a\n          matching path prefix.\n      - type: feature\n        title: Make the telemount Docker Volume plugin configurable\n        body: >-\n          A <code>telemount</code> object was added to the <code>intercept</code> object in <code>config.yml</code>\n          (or Helm value <code>client.intercept</code>), so that the automatic download and installation of this plugin can\n          be fully customised.\n      - type: feature\n        title: Add option to load the kubeconfig yaml from stdin during connect.\n        body: >-\n          This allows another process with a kubeconfig already loaded in memory\n          to directly pass it to <code>telepresence connect</code> without needing a separate\n          file. Simply use a dash \"-\" as the filename for the <code>--kubeconfig</code> flag.\n      - type: feature\n        title: Add ability to specify agent security context.\n        body: >-\n          A new Helm chart value <code>agent.securityContext</code> that will allow configuring the security context of\n          the injected traffic agent.  The value can be set to a valid Kubernetes securityContext object, or can be set\n          to an empty value (<code>{}</code>) to ensure the agent has no defined security context.  If no value is specified,\n          the traffic manager will set the agent's security context to the same as the first container's of the workload\n          being injected into.\n      - type: change\n        title: Tracing is no longer enabled by default.\n        body: >-\n          Tracing must now be enabled explicitly in order to use the <code>telepresence gather-traces</code>\n          command.\n      - type: change\n        title: Removal of timeouts that are no longer in use\n        body: >-\n          The <code>config.yml</code> values <code>timeouts.agentInstall</code> and <code>timeouts.apply</code> haven't\n          been in use since versions prior to 2.6.0, when the client was responsible for installing the traffic-agent.\n          These timeouts are now removed from the code-base, and a warning will be printed when attempts are made to use\n          them.\n      - type: bugfix\n        title: Search all private subnets to find one open for dnsServerSubnet\n        body: >-\n          This resolves a bug that did not test all subnets in a private range, sometimes resulting in the warning,\n          \"DNS doesn't seem to work properly.\"\n  - version: 2.18.4\n    date: (SUPERSEDED)\n    notes:\n      - type: bugfix\n        title: Docker aliases deprecation caused failure to detect Kind cluster.\n        body: >-\n          The logic for detecting if a cluster is a local Kind cluster, and therefore needs some special attention when\n          using <code>telepresence connect --docker</code>, relied on the presence of <code>Aliases</code> in the Docker\n          network that a Kind cluster sets up. In Docker versions from 26 and up, this value is no longer used, but the\n          corresponding info can instead be found in the new <code>DNSNames</code> field.\n  - version: 2.18.3\n    date: (SUPERSEDED)\n    notes:\n      - type: bugfix\n        title: Creation of individual pods was blocked by the agent-injector webhook.\n        body: >-\n          An attempt to create a pod was blocked unless it was provided by a workload. Hence, commands like\n          <code>kubectl run -i busybox --rm --image=curlimages/curl --restart=Never -- curl echo-easy.default</code>\n          would be blocked from executing.\n  - version: 2.18.2\n    date: (SUPERSEDED)\n    notes:\n      - type: bugfix\n        title: Fix panic due to root daemon not running.\n        body: >-\n          If a <code>telepresence connect</code> was made at a time when the root daemon was not running (an abnormal\n          condition) and a subsequent intercept was then made, a panic would occur when the port-forward to the agent\n          was set up. This is now fixed so that the initial <code>telepresence connect</code> is refused unless the root\n          daemon is running.\n  - version: 2.18.1\n    date: (SUPERSEDED)\n    notes:\n      - type: bugfix\n        title: Get rid of telemount plugin stickiness\n        body: >-\n          The <code>datawire/telemount</code> that is automatically downloaded and installed, would never be\n          updated once the installation was made. Telepresence will now check for the latest release of the\n          plugin and cache the result of that check for 24 hours. If a new version arrives, it will be\n          installed and used.\n      - type: bugfix\n        title: Use route instead of address for CIDRs with masks that don't allow \"via\"\n        body: >-\n          A CIDR with a mask that leaves less than two bits (/31 or /32 for IPv4)\n          cannot be added as an address to the VIF, because such addresses must\n          have bits allowing a \"via\" IP.\n\n          The logic was modified to allow such CIDRs to become static routes, using the\n          VIF base address as their \"via\", rather than being VIF addresses in their own right.\n      - type: bugfix\n        title: Containerized daemon created cache files owned by root\n        body: >-\n          When using <code>telepresence connect --docker</code> to create a containerized daemon, that\n          daemon would sometimes create files in the cache that were owned by root, which then caused\n          problems when connecting without the <code>--docker</code> flag.\n      - type: bugfix\n        title: Remove large number of requests when traffic-manager is used in large clusters.\n        body: >-\n          The traffic-manager would make a very large number of API requests during cluster start-up\n          or when many services were changed for other reasons. The logic that did this was refactored\n          and the number of queries were significantly reduced.\n      - type: bugfix\n        title: Don't patch probes on replaced containers.\n        body: >-\n          A container that is being replaced by a <code>telepresence intercept --replace</code>\n          invocation will have no liveness-, readiness, nor startup-probes. Telepresence didn't\n          take this into consideration when injecting the traffic-agent, but now it will refrain\n          from patching symbolic port names of those probes.\n      - type: bugfix\n        title: Don't rely on context name when deciding if a kind cluster is used.\n        body: >-\n          The code that auto-patches the kubeconfig when connecting to a kind cluster from within\n          a docker container, relied on the context name starting with \"kind-\", but although all\n          contexts created by kind have that name, the user is still free to rename it or to create\n          other contexts using the same connection properties. The logic was therefore changed\n          to instead look for a loopback service address.\n  - version: 2.18.0\n    date: \"2024-02-09\"\n    notes:\n      - type: feature\n        title: Include the image for the traffic-agent in the output of the version and status commands.\n        body: >-\n          The version and status commands will now output the image that the traffic-agent will be using when injected\n          by the agent-injector.\n      - type: feature\n        title: Custom DNS using the client DNS resolver.\n        body: >-\n          <p>A new <code>telepresence connect --proxy-via CIDR=WORKLOAD</code> flag was introduced, allowing Telepresence\n          to translate DNS responses matching specific subnets into virtual IPs that are used locally. Those virtual IPs\n          are then routed (with reverse translation) via the pod's of a given workload. This makes it possible to handle\n          custom DNS servers that resolve domains into loopback IPs. The flag may also be used in cases where the\n          cluster's subnets are in conflict with the workstation's VPN.</p>\n          <p>The CIDR can also be a symbolic name that identifies a subnet or list of subnets:</p><table><tbody>\n          <tr><td><code>also</code></td><td>All subnets added with --also-proxy</td></tr>\n          <tr><td><code>service</code></td><td>The cluster's service subnet</td></tr>\n          <tr><td><code>pods</code></td><td>The cluster's pod subnets.</td></tr>\n          <tr><td><code>all</code></td><td>All of the above.</td></tr>\n          </tbody></table>\n      - type: bugfix\n        title: Ensure that agent.appProtocolStrategy is propagated correctly.\n        body: >-\n          The <code>agent.appProtocolStrategy</code> was inadvertently dropped when moving license related code fromm the\n          OSS repository the repository for the Enterprise version of Telepresence. It has now been restored.\n      - type: bugfix\n        title: Include non-default zero values in output of telepresence config view.\n        body: >-\n          The <code>telepresence config view</code> command will now print zero values in the output when\n          the default for the value is non-zero.\n      - type: bugfix\n        title: Restore ability to run the telepresence CLI in a docker container.\n        body: >-\n          The improvements made to be able to run the telepresence daemon in docker\n          using <code>telepresence connect --docker</code> made it impossible to run\n          both the CLI and the daemon in docker. This commit fixes that and\n          also ensures that the user- and root-daemons are merged in this\n          scenario when the container runs as root.\n      - type: bugfix\n        title: Remote mounts when intercepting with the --replace flag.\n        body: >-\n          A <code>telepresence intercept --replace</code> did not correctly mount all volumes, because when the\n          intercepted container was removed, its mounts were no longer visible to the agent-injector when it\n          was subjected to a second invocation. The container is now kept in place, but with an image that\n          just sleeps infinitely.\n      - type: bugfix\n        title: Intercepting with the --replace flag will no longer require all subsequent intercepts to use --replace.\n        body: >-\n          A <code>telepresence intercept --replace</code> will no longer switch the mode of the intercepted workload,\n          forcing all subsequent intercepts on that workload to use <code>--replace</code> until the agent is\n          uninstalled. Instead, <code>--replace</code> can be used interchangeably just like any other intercept flag.\n      - type: bugfix\n        title: Kubeconfig exec authentication with context names containing colon didn't work on Windows\n        body: >-\n          The logic added to allow the root daemon to connect directly to the cluster using the user daemon as a proxy\n          for exec type authentication in the kube-config, didn't take into account that a context name sometimes\n          contains the colon \":\" character. That character cannot be used in filenames on windows because it is the\n          drive letter separator.\n      - type: bugfix\n        title: Provide agent name and tag as separate values in Helm chart\n        body: >-\n          The <code>AGENT_IMAGE</code> was a concatenation of the agent's name and tag. This is now changed so that the\n          env instead contains an <code>AGENT_IMAGE_NAME</code> and <code>AGENT_INAGE_TAG</code>. The <code>AGENT_IMAGE\n          </code> is removed. Also, a new env <code>REGISTRY</code> is added, where the registry of the traffic-\n          manager image is provided. The <code>AGENT_REGISTRY</code> is no longer required\n          and will default to <code>REGISTRY</code> if not set.\n      - type: bugfix\n        title: Environment interpolation expressions were prefixed twice.\n        body: >-\n          Telepresence would sometimes prefix environment interpolation expressions in the traffic-agent twice so\n          that an expression that looked like <code>$(SOME_NAME)</code> in the app-container, ended up as <code>\n          $(_TEL_APP_A__TEL_APP_A_SOME_NAME)</code> in the corresponding expression in the traffic-agent.\n      - type: bugfix\n        title: Panic in root-daemon on darwin workstations with full access to cluster network.\n        body: >-\n          A darwin machine with full access to the cluster's subnets will never create a TUN-device, and a check was\n          missing if the device actually existed, which caused a panic in the root daemon.\n      - type: bugfix\n        title: Show allow-conflicting-subnets in telepresence status and telepresence config view.\n        body: >-\n          The <code>telepresence status</code> and <code>telepresence config view</code> commands didn't show the\n          <code>allowConflictingSubnets</code> CIDRs because the value wasn't propagated correctly to the CLI.\n      - type: feature\n        title: It is now possible use a host-based connection and containerized connections simultaneously.\n        body: >-\n          Only one host-based connection can exist because that connection will alter the DNS to reflect the namespace\n          of the connection. but it's now possible to create additional connections using <code>--docker</code> while\n          retaining the host-based connection.\n      - type: feature\n        title: Ability to set the hostname of a containerized daemon.\n        body: >-\n          The hostname of a containerized daemon defaults to be the container's ID in Docker. You now can override the\n          hostname using <code>telepresence connect --docker --hostname &lt;a name&gt;</code>.\n      - type: feature\n        title: New <code>--multi-daemon</code>flag to enforce a consistent structure for the status command output.\n        body: >-\n          The output of the <code>telepresence status</code> when using <code>--output json</code> or <code>--output\n          yaml</code> will either show an object where the <code>user_daemon</code> and <code>root_daemon</code>\n          are top level elements, or when multiple connections are used, an object where a <code>connections</code>\n          list contains objects with those daemons. The flag <code>--multi-daemon</code> will enforce the latter\n          structure even when only one daemon is connected so that the output can be parsed consistently. The reason\n          for keeping the former structure is to retain backward compatibility with existing parsers.\n      - type: bugfix\n        title: Make output from telepresence quit more consistent.\n        body: >-\n          A quit (without -s) just disconnects the host user and root daemons but will quit a container based daemon.\n          The message printed was simplified to remove some have/has is/are errors caused by the difference.\n      - type: bugfix\n        title: \"Fix &quot;tls: bad certificate&quot; errors when refreshing the mutator-webhook secret\"\n        body: >-\n          The <code>agent-injector</code> service will now refresh the secret used by the <code>mutator-webhook</code>\n          each time a new connection is established, thus preventing the certificates to go out-of-sync when\n          the secret is regenerated.\n      - type: bugfix\n        title: Keep telepresence-agents configmap in sync with pod states.\n        body: >-\n          An intercept attempt that resulted in a timeout due to failure of injecting the traffic-agent left the\n          <code>telepresence-agents</code> configmap in a state that indicated that an agent had been added, which\n          caused problems for subsequent intercepts after the problem causing the first failure had been fixed.\n      - type: bugfix\n        title: The <code>telepresence status</code> command will now report the status of all running daemons.\n        body: >-\n          A <code>telepresence status</code>, issued when multiple containerized daemons were active, would error with\n          &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now\n          fixed so that the command instead reports the status of all running daemons.\n      - type: bugfix\n        title: The <code>telepresence version</code> command will now report the version of all running daemons.\n        body: >-\n          A <code>telepresence version</code>, issued when multiple containerized daemons were active, would error with\n          &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now\n          fixed so that the command instead reports the version of all running daemons.\n      - type: bugfix\n        title: Multiple containerized daemons can now be disconnected using <code>telepresence quit -s</code>\n        body: >-\n          A <code>telepresence quit -s</code>, issued when multiple containerized daemons were active, would error with\n          &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now\n          fixed so that the command instead quits all daemons.\n      - type: bugfix\n        title: The DNS search path on Windows is now restored when Telepresence quits\n        body: >-\n          The DNS search path that Telepresence uses to simulate the DNS lookup functionality in the connected\n          cluster namespace was not removed by a <code>telepresence quit</code>, resulting in connectivity problems\n          from the workstation. Telepresence will now remove the entries that it has added to the search list when\n          it quits.\n      - type: bugfix\n        title: The user-daemon would sometimes get killed when used by multiple simultaneous CLI clients.\n        body: >-\n          The user-daemon would die with a fatal &quot;fatal error: concurrent map writes&quot; error in the\n          <code>connector.log</code>, effectively killing the ongoing connection.\n      - type: bugfix\n        title: Multiple services ports using the same target port would not get intercepted correctly.\n        body: >-\n          Intercepts didn't work when multiple service ports were using the same container port. Telepresence would\n          think that one of the ports wasn't intercepted and therefore disable the intercept of the container port.\n      - type: bugfix\n        title: Root daemon refuses to disconnect.\n        body: >-\n          The root daemon would sometimes hang forever when attempting to disconnect due to a deadlock in\n          the VIF-device.\n      - type: bugfix\n        title: Fix panic in user daemon when traffic-manager was unreachable\n        body: >-\n          The user daemon would panic if the traffic-manager was unreachable. It will now instead report\n          a proper error to the client.\n      - type: change\n        title: Removal of backward support for versions predating 2.6.0\n        body: >-\n          The telepresence helm installer will no longer discover and convert workloads that were modified by versions\n          prior to 2.6.0. The traffic manager will and no longer support the muxed tunnels used in versions prior to\n          2.5.0.\n  - version: 2.17.0\n    date: \"2023-11-14\"\n    notes:\n      - type: feature\n        title: Additional Prometheus metrics to track intercept/connect activity\n        body: >-\n          This feature adds the following metrics to the Prometheus endpoint: <code>connect_count</code>,\n          <code>connect_active_status</code>, <code>intercept_count</code>, and <code>intercept_active_status</code>.\n          These are labeled by client/install_id.\n          Additionally, the <code>intercept_count</code> metric has been renamed to <code>active_intercept_count</code>\n          for clarity.\n      - type: feature\n        title: Make the Telepresence client docker image configurable.\n        body: >-\n          The docker image used when running a Telepresence intercept in docker mode can now be configured using\n          the setting <code>images.clientImage</code> and will default first to the value of the environment <code>\n          TELEPRESENCE_CLIENT_IMAGE</code>, and then to the value preset by the telepresence binary. This\n          configuration setting is primarily intended for testing purposes.\n      - type: feature\n        title: Use traffic-agent port-forwards for outbound and intercepted traffic.\n        body: >-\n          The telepresence TUN-device is now capable of establishing direct port-forwards to a traffic-agent in the\n          connected namespace. That port-forward is then used for all outbound traffic to the device, and also for\n          all traffic that arrives from intercepted workloads. Getting rid of the extra hop via the traffic-manager\n          improves performance and reduces the load on the traffic-manager. The feature can only be used if the client\n          has Kubernetes port-forward permissions to the connected namespace. It can be disabled by setting <code>\n          cluster.agentPortForward</code> to <code>false</code> in <code>config.yml</code>.\n      - type: feature\n        title: Improve outbound traffic performance.\n        body: >-\n          The root-daemon now communicates directly with the traffic-manager instead of routing all outbound traffic\n          through the user-daemon. The root-daemon uses a patched kubeconfig where <code>exec</code> configurations to\n          obtain credentials are dispatched to the user-daemon. This to ensure that all authentication plugins will\n          execute in user-space. The old behavior of routing everything through the user-daemon can be restored by\n          setting <code>cluster.connectFromRootDaemon</code> to <code>false</code> in <code>config.yml</code>.\n      - type: feature\n        title: New networking CLI flag --allow-conflicting-subnets\n        body: >-\n          telepresence connect (and other commands that kick off a connect) now accepts an --allow-conflicting-subnets\n          CLI flag. This is equivalent to client.routing.allowConflictingSubnets in the helm chart, but can be specified\n          at connect time. It will be appended to any configuration pushed from the traffic manager.\n      - type: change\n        title: Warn if large version mismatch between traffic manager and client.\n        body: >-\n          Print a warning if the minor version diff between the client and the traffic manager is greater than three.\n      - type: change\n        title: The authenticator binary was removed from the docker image.\n        body: >-\n          The <code>authenticator</code> binary, used when serving proxied <code>exec</code> kubeconfig credential\n          retrieval, has been removed. The functionality was instead added as a subcommand to the <code>telepresence\n          </code> binary.\n  - version: 2.16.1\n    date: \"2023-10-12\"\n    notes:\n      - type: feature\n        title: Add --docker-debug flag to the telepresence intercept command.\n        body: >-\n          This flag is similar to <code>--docker-build</code> but will start the container with more relaxed security\n          using the <code>docker run</code> flags <code>--security-opt apparmor=unconfined --cap-add SYS_PTRACE</code>.\n      - type: feature\n        title: Add a --export option to the telepresence connect command.\n        body: >-\n          In some situations it is necessary to make some ports available to the\n          host from a containerized telepresence daemon. This commit adds a\n          repeatable <code>--expose &lt;docker port exposure&gt;</code> flag to the connect\n          command.\n      - type: feature\n        title: Prevent agent-injector webhook from selecting from kube-xxx namespaces.\n        body: >-\n          The <code>kube-system</code> and <code>kube-node-lease</code> namespaces should not be affected by a\n          global agent-injector webhook by default. A default <code>namespaceSelector</code> was therefore added\n          to the Helm Chart <code>agentInjector.webhook</code> that contains a <code>NotIn</code> preventing those\n          namespaces from being selected.\n      - type: bugfix\n        title: Backward compatibility for pod template TLS annotations.\n        body: >-\n          Users of Telepresence &lt; 2.9.0 that make use of the pod template TLS annotations were unable to upgrade because\n          the annotation names have changed (now prefixed by \"telepresence.\"), and the environment expansion of the\n          annotation values was dropped. This fix restores support for the old names (while retaining the new ones) and\n          the environment expansion.\n      - type: security\n        title: Built with go 1.21.3\n        body: >-\n          Built Telepresence with go 1.21.3 to address CVEs.\n      - type: bugfix\n        title: Match service selector against pod template labels\n        body: >-\n          When listing intercepts (typically by calling <code>telepresence list</code>) selectors of services are matched\n          against workloads. Previously the match was made against the labels of the workload, but now they are matched\n          against the labels pod template of the workload. Since the service would actually be matched against pods this\n          is more correct. The most common case when this makes a difference is that statefulsets now are listed when they should.\n  - version: 2.16.0\n    date: \"2023-10-02\"\n    notes:\n      - type: bugfix\n        title: The helm sub-commands will no longer start the user daemon.\n        body: >-\n          The <code>telepresence helm install/upgrade/uninstall</code> commands will no longer start the telepresence\n          user daemon because there's no need to connect to the traffic-manager in order for them to execute.\n      - type: bugfix\n        title: Routing table race condition\n        body: >-\n          A race condition would sometimes occur when a Telepresence TUN device was deleted and another created in rapid\n          succession that caused the routing table to reference interfaces that no longer existed.\n      - type: bugfix\n        title: Stop lingering daemon container\n        body: >-\n          When using <code>telepresence connect --docker</code>, a lingering container could be present, causing errors\n          like &quot;The container name NN is already in use by container XX ...&quot;. When this happens, the connect\n          logic will now give the container some time to stop and then call <code>docker stop NN</code> to stop it\n          before retrying to start it.\n      - type: bugfix\n        title: Add file locking to the Telepresence cache\n        body: >-\n          Files in the Telepresence cache are accesses by multiple processes. The processes will now use advisory\n          locks on the files to guarantee consistency.\n      - type: change\n        title: Lock connection to namespace\n        body: >-\n          The behavior changed so that a connected Telepresence client is bound to a namespace. The namespace can then\n          not be changed unless the client disconnects and reconnects. A connection is also given a name. The default\n          name is composed from <code>&lt;kube context name&gt;-&lt;namespace&gt;</code> but can be given explicitly\n          when connecting using <code>--name</code>. The connection can optionally be identified using the option\n          <code>--use &lt;name match&gt;</code> (only needed when docker is used and more than one connection is active).\n      - type: change\n        title: Deprecation of global --context and --docker flags.\n        body: >-\n          The global flags <code>--context</code> and <code>--docker</code> will now be considered deprecated unless used\n          with commands that accept the full set of Kubernetes flags (e.g. <code>telepresence connect</code>).\n      - type: change\n        title: Deprecation of the --namespace flag for the intercept command.\n        body: >-\n          The <code>--namespace</code> flag is now deprecated for <code>telepresence intercept</code> command. The flag can instead\n          be used with all commands that accept the full set of Kubernetes flags (e.g. <code>telepresence connect</code>).\n      - type: change\n        title: Legacy code predating version 2.6.0 was removed.\n        body: >-\n          The telepresence code-base still contained a lot of code that would modify workloads instead of relying on\n          the mutating webhook installer when a traffic-manager version predating version 2.6.0 was discovered. This\n          code has now been removed.\n      - type: feature\n        title: Add `telepresence list-namespaces` and `telepresence list-contexts` commands\n        body: >-\n          These commands can be used to check accessible namespaces and for automation.\n      - type: change\n        title: Implicit connect warning\n        body: >-\n          A deprecation warning will be printed if a command other than <code>telepresence connect</code> causes an\n          implicit connect to happen. Implicit connects will be removed in a future release.\n  - version: 2.15.1\n    date: \"2023-09-06\"\n    notes:\n      - type: security\n        title: Rebuild with go 1.21.1\n        body: >-\n          Rebuild Telepresence with go 1.21.1 to address CVEs.\n      - type: security\n        title: Set security context for traffic agent\n        body: >-\n          Openshift users reported that the traffic agent injection was failing due to a missing security context.\n  - version: 2.15.0\n    date: \"2023-08-29\"\n    notes:\n      - type: security\n        title: Add ASLR to telepresence binaries\n        body: >-\n          ASLR hardens binary sercurity against fixed memory attacks.\n      - type: feature\n        title: Added client builds for arm64 architecture.\n        body: >-\n          Updated the release workflow files in github actions to including building and publishing the client binaries for arm64 architecture.\n        docs: https://github.com/telepresenceio/telepresence/issues/3259\n      - type: bugfix\n        title: KUBECONFIG env var can now be used with the docker mode.\n        body: >-\n          If provided, the KUBECONFIG environment variable was passed to the kubeauth-foreground service as a parameter.\n          However, since it didn't exist, the CLI was throwing an error when using <code>telepresence connect --docker</code>.\n        docs: https://github.com/telepresenceio/telepresence/pull/3300\n      - type: bugfix\n        title: Fix deadlock while watching workloads\n        body: >-\n          The <code>telepresence list --output json-stream</code> wasn't releasing the session's lock after being\n          stopped, including with a <code>telepresence quit</code>. The user could be blocked as a result.\n        docs: https://github.com/telepresenceio/telepresence/pull/3298\n      - type: bugfix\n        title: Change json output of telepresence list command\n        body: >-\n          Replace deprecated info in the JSON output of the telepresence list command.\n  - version: 2.14.4\n    date: \"2023-08-21\"\n    notes:\n      - type: bugfix\n        title: Nil pointer exception when upgrading the traffic-manager.\n        body: >-\n          Upgrading the traffic-manager using <code>telepresence helm upgrade</code> would sometimes\n          result in a helm error message <q>executing \"telepresence/templates/intercept-env-configmap.yaml\"\n          at &lt;.Values.intercept.environment.excluded&gt;: nil pointer evaluating interface {}.excluded\"</q>\n        docs: https://github.com/telepresenceio/telepresence/issues/3313\n  - version: 2.14.2\n    date: \"2023-07-26\"\n    notes:\n      - type: bugfix\n        title: Telepresence now use the OSS agent in its latest version by default.\n        body: >-\n          The traffic manager admin was forced to set it manually during the chart installation.\n        docs: https://github.com/telepresenceio/telepresence/issues/3271\n  - version: 2.14.1\n    date: \"2023-07-07\"\n    notes:\n      - type: feature\n        title: Envoy's http idle timout is now configurable.\n        body: >-\n          A new <code>agent.helm.httpIdleTimeout</code> setting was added to the Helm chart that controls\n          the proprietary Traffic agent's http idle timeout. The default of one hour, which in some situations\n          would cause a lot of resource consuming and lingering connections, was changed to 70 seconds.\n      - type: feature\n        title: Add more gauges to the Traffic manager's Prometheus client.\n        body: >-\n          Several gauges were added to the Prometheus client to make it easier to monitor\n          what the Traffic manager spends resources on.\n      - type: feature\n        title: Agent Pull Policy\n        body: >-\n          Add option to set traffic agent pull policy in helm chart.\n      - type: bugfix\n        title: Resource leak in the Traffic manager.\n        body: >-\n          Fixes a resource leak in the Traffic manager caused by lingering tunnels between the clients and\n          Traffic agents. The tunnels are now closed correctly when terminated from the side that created them.\n      - type: bugfix\n        title: Fixed problem setting traffic manager namespace using the kubeconfig extension.\n        body: >-\n          Fixes a regression introduced in version 2.10.5, making it impossible to set the traffic-manager namespace\n          using the telepresence.io kubeconfig extension.\n        docs: https://www.telepresence.io/docs/reference/config#manager\n  - version: 2.14.0\n    date: \"2023-06-12\"\n    notes:\n      - type: feature\n        title: DNS configuration now supports excludes and mappings.\n        body: >-\n          The DNS configuration now supports two new fields, excludes and mappings. The excludes field allows you to\n          exclude a given list of hostnames from resolution, while the mappings field can be used to resolve a hostname with\n          another.\n        docs: https://github.com/telepresenceio/telepresence/pull/3172\n\n      - type: feature\n        title: Added the ability to exclude environment variables\n        body: >-\n          Added a new config map that can take an array of environment variables that will\n          then be excluded from an intercept that retrieves the environment of a pod.\n\n      - type: bugfix\n        title: Fixed traffic-agent backward incompatibility issue causing lack of remote mounts\n        body: >-\n          A traffic-agent of version 2.13.3 (or 1.13.15) would not propagate the directories under\n          <code>/var/run/secrets</code> when used with a traffic manager older than 2.13.3.\n\n      - type: bugfix\n        title: Fixed race condition causing segfaults on rare occasions when a tunnel stream timed out.\n        body: >-\n          A context cancellation could sometimes be trapped in a stream reader, causing it to incorrectly return\n          an undefined message which in turn caused the parent reader to panic on a <code>nil</code> pointer reference.\n        docs: https://github.com/telepresenceio/telepresence/pull/2963\n\n      - type: change\n        title: Routing conflict reporting.\n        body: >-\n          Telepresence will now attempt to detect and report routing conflicts with other running VPN software on client machines.\n          There is a new configuration flag that can be tweaked to allow certain CIDRs to be overridden by Telepresence.\n\n      - type: change\n        title: test-vpn command deprecated\n        body: >-\n          Running telepresence test-vpn will now print a deprecation warning and exit. The command will be removed in a future release.\n          Instead, please configure telepresence for your VPN's routes.\n  - version: 2.13.3\n    date: \"2023-05-25\"\n    notes:\n      - type: feature\n        title: Add imagePullSecrets to hooks\n        body: >-\n          Add .Values.hooks.curl.imagePullSecrets and .Values.hooks curl.imagePullSecrets to Helm values.\n        docs: https://github.com/telepresenceio/telepresence/pull/3079\n\n      - type: change\n        title: Change reinvocation policy to Never for the mutating webhook\n        body: >-\n          The default setting of the reinvocationPolicy for the mutating webhook dealing with agent injections changed from Never to IfNeeded.\n\n      - type: bugfix\n        title: Fix mounting fail of IAM roles for service accounts web identity token\n        body: >-\n          The eks.amazonaws.com/serviceaccount volume injected by EKS is now exported and remotely mounted during an intercept.\n        docs: https://github.com/telepresenceio/telepresence/issues/3166\n\n      - type: bugfix\n        title: Correct namespace selector for cluster versions with non-numeric characters\n        body: >-\n          The mutating webhook now correctly applies the namespace selector even if the cluster version contains non-numeric characters. For example, it can now handle versions such as Major:\"1\", Minor:\"22+\".\n        docs: https://github.com/telepresenceio/telepresence/pull/3184\n\n      - type: bugfix\n        title: Enable IPv6 on the telepresence docker network\n        body: >-\n          The \"telepresence\" Docker network will now propagate DNS AAAA queries to the Telepresence DNS resolver when it runs in a Docker container.\n        docs: https://github.com/telepresenceio/telepresence/issues/3179\n\n      - type: bugfix\n        title: Fix the crash when intercepting with --local-only and --docker-run\n        body: >-\n          Running telepresence intercept --local-only --docker-run no longer  results in a panic.\n        docs: https://github.com/telepresenceio/telepresence/issues/3171\n\n      - type: bugfix\n        title: Fix incorrect error message with local-only mounts\n        body: >-\n          Running telepresence intercept --local-only --mount false no longer results in an incorrect error message saying \"a local-only intercept cannot have mounts\".\n        docs: https://github.com/telepresenceio/telepresence/issues/3171\n\n      - type: bugfix\n        title: specify port in hook urls\n        body: >-\n          The helm chart now correctly handles custom agentInjector.webhook.port that was not being set in hook URLs.\n        docs: https://github.com/telepresenceio/telepresence/pull/3161\n\n      - type: bugfix\n        title: Fix wrong default value for disableGlobal and agentArrival\n        body: >-\n          Params .intercept.disableGlobal and .timeouts.agentArrival are now correctly honored.\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance for contributors and AI assistants working with this repository.\n\n## Project Overview\n\nTelepresence is a Kubernetes development tool that enables fast local development by connecting your local workstation to a Kubernetes cluster. It allows developers to run services locally while accessing cluster resources and intercepting traffic from the cluster to their local machine.\n\n## Git Workflow\n\n- Never commit directly to the `release/v2` branch. Always create a feature branch with a name following the pattern `username/topic` (e.g., `thallgren/fix-dns-resolution`).\n- All commits must be signed and signed-off (`git commit -s -S`).\n- Push the branch and create a pull request for review.\n- Always merge PRs with a merge commit (never squash or rebase).\n\n## Build Artifacts\n\nThe Open Source version of Telepresence consists of three artifacts:\n\n**Client-side (runs on developer workstation):**\n- **`telepresence` binary** - The same binary serves as CLI, user daemon, and root daemon.\n- **`telepresence` Docker image** - Used as both user and root daemon when running `telepresence connect --docker`.\n\n**Cluster-side (runs in Kubernetes):**\n- **`tel2` Docker image** - Used by the traffic-manager deployment and injected as traffic-agent sidecars.\n\n## Build Commands\n\n```bash\n# Set required environment variables\nexport TELEPRESENCE_VERSION=v2.x.x-alpha.0  # or use auto-generated version\nexport TELEPRESENCE_REGISTRY=local          # 'local' for Docker Desktop, or 'ghcr.io/telepresenceio'\n\n# Build the telepresence binary\nmake build\n\n# Build Docker images (for local Kubernetes development)\nmake client-image    # Client container image\nmake tel2-image      # Traffic-manager/traffic-agent image\n\n# Build everything for local development\nmake build client-image tel2-image\n\n# Install to system\nmake install\n\n# Clean build artifacts\nmake clean\nmake clobber  # Also removes tools\n```\n\nEnvironment variables:\n- `TELEPRESENCE_REGISTRY` (required) - Docker registry for images. Use `local` for docker-based Kubernetes, or `ghcr.io/telepresenceio` for the release registry.\n- `TELEPRESENCE_VERSION` (optional) - Version string to compile into binaries and images. If not set, auto-generated from CHANGELOG.yml and source hash.\n\nRun `make help` for more information.\n\n### Building on Windows\n\nWindows builds use `build-aux\\winmake.bat` instead of `make` directly. Pass the same parameters as you would to make. The script runs make inside a Docker container with appropriate parameters for Windows binaries.\n\n## Testing\n\n```bash\n# Run unit tests\nmake check-unit\n\n# Run all integration tests (requires Kubernetes cluster)\nmake check-integration\n\n# Run a single integration test\ngo test ./integration_test/... -v -testify.m=Test_InterceptDetailedOutput\n\n# Run an integration test suite\nTEST_SUITE='^WorkloadConfiguration$' go test ./integration_test/... -v\n\n# Build tests without running (useful for caching)\nmake build-tests\n```\n\nIntegration tests use testify suites. The test harness is in `integration_test/itest/`. Use `-testify.m=<pattern>` to filter tests by name. Verbose output (`-v`) is recommended as tests produce human-readable output with timestamps that correlate with log files.\n\n### Integration Test Environment Variables\n\n| Environment Name           | Description                                   | Default                   |\n|----------------------------|-----------------------------------------------|---------------------------|\n| `DEV_KUBECONFIG`           | Cluster configuration used by the tests       | Kubernetes default        |\n| `DEV_CLIENT_REGISTRY`      | Docker registry for the client image          | ${TELEPRESENCE_REGISTRY}  |\n| `DEV_MANAGER_REGISTRY`     | Docker registry for the traffic-manager image | ${TELEPRESENCE_REGISTRY}  |\n| `DEV_AGENT_REGISTRY`       | Docker registry for the traffic-agent image   | Traffic-manager registry  |\n| `DEV_CLIENT_IMAGE`         | Name of the client image                      | \"telepresence\"            |\n| `DEV_MANAGER_IMAGE`        | Name of the traffic-manager image             | \"tel2\"                    |\n| `DEV_AGENT_IMAGE`          | Name of the traffic-agent image               | Traffic-manager image     |\n| `DEV_CLIENT_VERSION`       | Client version                                | ${TELEPRESENCE_VERSION#v} |\n| `DEV_MANAGER_VERSION`      | Traffic-manager version                       | ${TELEPRESENCE_VERSION#v} |\n| `DEV_AGENT_VERSION`        | Traffic-agent image version                   | Traffic-manager version   |\n| `DEV_USERD_PROFILING_PORT` | Start user daemon with pprof enabled          |                           |\n| `DEV_ROOTD_PROFILING_PORT` | Start root daemon with pprof enabled          |                           |\n| `TEST_SUITE`               | Regexp matching test suite name(s)            |                           |\n\nThese can also be provided in an `itest.yml` file placed next to `config.yml`:\n\n```yaml\nEnv:\n  DEV_CLIENT_VERSION: v2.x.x-alpha.0\n  DEV_KUBECONFIG: /path/to/kubeconfig\nConfig:\n  docker:\n    addHostGateway: false\n```\n\n### Using Docker Desktop with Kubernetes\n\nUsing Kubernetes bundled with Docker Desktop is the quickest way to run tests. No need to push images to a registry - Kubernetes finds them in Docker's local cache. Integration tests automatically use `pullPolicy=Never` when `DEV_CLIENT_REGISTRY` is set to \"local\".\n\n```bash\nexport TELEPRESENCE_VERSION=v2.x.x-alpha.0\nexport TELEPRESENCE_REGISTRY=local\nmake build client-image tel2-image\ngo test ./integration_test/... -v -testify.m=Test_InterceptDetailedOutput\n```\n\n## Linting\n\n```bash\n# Run all linters\nmake lint\n\n# Run Go linter only\nmake lint-go\n\n# Run protobuf linter only\nmake lint-rpc\n\n# Auto-fix lint issues\nmake format\n```\n\nLinting uses golangci-lint v2 running in Docker. Configuration is in `.golangci.yml`.\n\n## Code Generation\n\n```bash\n# Regenerate protobuf and license files\nmake generate\n\n# Regenerate protobuf files only\nmake protoc\n\n# Regenerate documentation files (after changing CHANGELOG.yml)\nmake docs-files\n```\n\n**Important:** After modifying `CHANGELOG.yml`, always run `make docs-files` to regenerate documentation files (`docs/release-notes.md`, `docs/release-notes.mdx`, `docs/variables.yml`).\n\n**Important:** All files under `docs/reference/cli/` are generated from Go source code. Do not edit them directly; instead, modify the corresponding Go source and regenerate.\n\n### Updating License Documentation\n\nRun `make generate` and commit changes to `DEPENDENCY_LICENSES.md` and `DEPENDENCIES.md`.\n\n## Architecture\n\n### Main Components\n\n1. **CLI/Client** (`cmd/telepresence/`, `pkg/client/cli/`)\n   - Single binary serving as CLI, user daemon, and root daemon\n   - Commands are in `pkg/client/cli/cmd/`\n\n2. **User Daemon (userd)** (`pkg/client/userd/`)\n   - Runs as the user, manages connection to traffic-manager\n   - Handles intercepts, port forwards, cluster communication\n\n3. **Root Daemon (rootd)** (`pkg/client/rootd/`)\n   - Runs with elevated privileges\n   - Manages virtual network interface (VIF) and DNS\n\n4. **Traffic Manager** (`cmd/traffic/cmd/manager/`)\n   - Runs in the Kubernetes cluster (ambassador namespace by default)\n   - Coordinates intercepts between clients and traffic-agents\n\n5. **Traffic Agent** (`cmd/traffic/cmd/agent/`)\n   - Injected as sidecar into intercepted pods\n   - Routes traffic between the pod and the local machine\n\n6. **Agent Init** (`cmd/traffic/cmd/agentinit/`)\n   - Init container for setting up iptables rules in pods\n\n7. **Docker Network Driver** (`cmd/teleroute/`)\n   - Only used when connecting with `--docker` flag\n   - Provides the Docker network that enables communication between the Telepresence daemon container and other containers\n\n### Key Packages\n\n- `pkg/vif/` - Virtual network interface implementation\n- `pkg/tunnel/` - gRPC-based tunneling for network traffic\n- `pkg/dnsproxy/` - DNS resolution and proxying\n- `pkg/agentconfig/` - Traffic-agent configuration\n- `pkg/client/k8s/` - Kubernetes client interactions\n- `pkg/routing/` - Network routing logic\n- `pkg/client/cli/cmd/` - CLI commands. One per file.\n\n### RPC Definitions\n\nProtocol buffers are in `rpc/` with separate packages:\n- `rpc/connector/` - Client-to-userd communication\n- `rpc/daemon/` - Client-to-rootd communication\n- `rpc/manager/` - Client/userd-to-traffic-manager communication\n- `rpc/agent/` - Traffic-manager-to-traffic-agent communication\n\n### Helm Chart\n\nThe traffic-manager Helm chart is in `charts/telepresence-oss/`.\n\n## Debugging and Troubleshooting\n\n### Log Files\n\nThere are three log files:\n- `connector.log` - Output from user daemon: traffic-manager interaction, intercepts, port forwards\n- `daemon.log` - Output from root daemon: networking changes on your workstation\n- `cli.log` - Output from the command line interface\n\nLocations:\n- macOS: `~/Library/Logs/telepresence/`\n- Linux: `~/.cache/telepresence/logs/`\n- Windows: `%USERPROFILE%\\AppData\\Local\\logs`\n\nLogs rotate daily. Use `tail -F <filename>` to watch rotating logs seamlessly.\n\n### Debugging Early-Initialization Errors\n\nIf daemons fail during early initialization before logfiles are set up, run them directly to see stderr output. The `--address` flag is mandatory:\n\n```bash\n# Run user daemon directly\ntelepresence userd --logfile - --address :8083\n\n# Run root daemon directly (requires sudo)\nsudo telepresence rootd --logfile - --address :8084\n```\n\n### Profiling the Daemons\n\nEnable [pprof](https://pkg.go.dev/net/http/pprof) profiling:\n\n```bash\ntelepresence quit -s\ntelepresence connect --userd-profiling-port 6060 --rootd-profiling-port 6061\n# Then browse http://localhost:6060/debug/pprof/\n```\n\n### Dumping Goroutine Stacks\n\nSend SIGQUIT to a daemon to dump goroutine stacks to its log file. On Windows, use profiling instead.\n\n### RBAC Testing\n\nTo test with limited RBAC privileges:\n\n```bash\nkubectl apply -f k8s/client_rbac.yaml\nkubectl get sa telepresence-test-developer -o \"jsonpath={.secrets[0].name}\"\n# Get the token from the secret and configure kubectl\nkubectl get secret <secret-name> -o \"jsonpath={.data.token}\" | base64 --decode\nkubectl config set-credentials telepresence-test-developer --token <token>\nkubectl config use-context telepresence-test-developer\n```\n\n## Releases\n\nTo create a release, set `TELEPRESENCE_VERSION` and run `make prepare-release`. This creates two annotated tags (`vX.Y.Z` and `rpc/vX.Y.Z`) and a commit updating go.mod references. Pushing the tags and branch triggers the release workflow.\n\n```bash\n# Test release (marked as pre-release, not promoted to latest)\nexport TELEPRESENCE_VERSION=v2.27.0-test.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# Release candidate\nexport TELEPRESENCE_VERSION=v2.27.0-rc.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n\n# GA release (becomes \"latest\", updates Homebrew)\nexport TELEPRESENCE_VERSION=v2.27.0\nmake prepare-release\ngit push origin HEAD $TELEPRESENCE_VERSION rpc/$TELEPRESENCE_VERSION\n```\n\nVersion formats:\n- `vX.Y.Z-test.N` - Test release (pre-release)\n- `vX.Y.Z-rc.N` - Release candidate (pre-release)\n- `vX.Y.Z` - GA release (marked as latest, triggers Homebrew update)\n\n### Changelog\n\nWhen adding entries to `CHANGELOG.yml` for an upcoming release:\n- Use `date: (TBD)` for unreleased versions\n- The `make prepare-release` command will set the actual date when `TELEPRESENCE_VERSION` is a GA version (e.g., `v2.27.0`)\n- After modifying `CHANGELOG.yml`, run `make docs-files` to regenerate documentation\n\n### Documentation Website\n\nThe documentation website at [telepresence.io](https://telepresence.io) is managed in the [telepresenceio/telepresence.io](https://github.com/telepresenceio/telepresence.io) repository. When creating a GA release, update the website by running `make generate-version` in that repository with:\n- `DOCS_BRANCH` - Branch in this repository containing the docs (e.g., `release/v2`)\n- `DOCS_VERSION` - Major.minor version to generate or update (e.g., `2.27`)\n\nSee the telepresence.io repository for full instructions.\n\n### macOS Installer Signing and Notarization\n\nThe macOS `.pkg` installers are signed and notarized to pass Gatekeeper verification. The signing process uses a protected GitHub Environment to secure the signing credentials.\n\n#### Environment Setup\n\nThe `build-macos-pkg` job uses the `macos-signing` environment, which must be configured in the repository settings:\n\n1. Go to https://github.com/telepresenceio/telepresence/settings/environments\n2. Create an environment named `macos-signing`\n3. Enable \"Required reviewers\" and add authorized personnel\n4. Optionally restrict deployment branches to `release/*`\n5. Add the following secrets to the environment (not repository-level):\n\n| Secret Name | Description |\n|-------------|-------------|\n| `MACOS_CERTIFICATE_P12` | Base64-encoded P12 file containing both Application and Developer ID Installer certificates |\n| `MACOS_CERTIFICATE_PASSWORD` | Password for the P12 file |\n| `MACOS_SIGN_APPLICATION` | Developer ID Application certificate name (e.g., `Developer ID Application: Your Name (TEAMID)`) |\n| `MACOS_SIGN_INSTALLER` | Developer ID Installer certificate name (e.g., `Developer ID Installer: Your Name (TEAMID)`) |\n| `MACOS_NOTARIZE_APPLE_ID` | Apple ID email for notarization |\n| `MACOS_NOTARIZE_TEAM_ID` | Apple Developer Team ID |\n| `MACOS_NOTARIZE_PASSWORD` | App-specific password for notarization |\n\n#### Release Workflow\n\nWhen a release tag is pushed:\n1. All platform binaries (Linux, Windows, macOS) are built immediately\n2. Linux `.deb`/`.rpm` and Windows `.exe` installers are built\n3. The release is published with all binaries and Linux/Windows installers\n4. The `build-macos-pkg` job waits for approval from a required reviewer\n5. Once approved, signed `.pkg` installers are built and added to the release\n\nThis design ensures:\n- **Emergency releases can proceed** without the signing approver being available (all binaries and Linux/Windows installers are released)\n- **Signing credentials are protected** by requiring explicit approval before they are exposed\n- **Signed packages are added later** when the approver reviews and approves the job\n\nIf the environment is not configured or never approved, the release will contain macOS standalone binaries but not `.pkg` installers.\n\n#### Obtaining the Certificates\n\nYou need an [Apple Developer Program](https://developer.apple.com/programs/) membership ($99/year) to obtain signing certificates.\n\n1. **Create certificates in Apple Developer Portal:**\n   - Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/certificates/list)\n   - Click the + button to create a new certificate\n   - Create **Developer ID Application** certificate (for signing binaries)\n   - Create **Developer ID Installer** certificate (for signing .pkg files)\n   - Download both certificates and double-click to install in Keychain Access\n\n2. **Find your Team ID:**\n   - Go to [Membership Details](https://developer.apple.com/account#MembershipDetailsCard)\n   - Copy the Team ID (10-character alphanumeric string)\n   - Set as `MACOS_NOTARIZE_TEAM_ID`\n\n3. **Find the certificate names:**\n   - Open Keychain Access and look under \"My Certificates\"\n   - The names will be like:\n     - `Developer ID Application: Your Name (TEAMID)` → `MACOS_SIGN_APPLICATION`\n     - `Developer ID Installer: Your Name (TEAMID)` → `MACOS_SIGN_INSTALLER`\n   - You can also list them with: `security find-identity -v -p codesigning`\n\n4. **Export certificates to P12:**\n   ```bash\n   # Export each certificate from Keychain Access:\n   # - Right-click certificate → Export\n   # - Choose .p12 format\n   # - Set a strong password (will be MACOS_CERTIFICATE_PASSWORD)\n\n   # If you have both in separate .p12 files, you can import them together\n   # or export them together from Keychain Access by selecting both\n\n   # Base64-encode for GitHub secrets:\n   base64 -i certificates.p12 | pbcopy\n   # Paste as MACOS_CERTIFICATE_P12\n   ```\n\n5. **Create app-specific password for notarization:**\n   - Go to [appleid.apple.com](https://appleid.apple.com/) → Sign-In and Security → App-Specific Passwords\n   - Generate a new password with a descriptive name (e.g., \"GitHub Actions Notarization\")\n   - Copy the generated password → `MACOS_NOTARIZE_PASSWORD`\n   - Use your Apple ID email → `MACOS_NOTARIZE_APPLE_ID`\n\n#### Testing Locally\n\nTo test signing locally before configuring GitHub secrets:\n\n```bash\n# Set environment variables\nexport MACOS_SIGN_APPLICATION=\"Developer ID Application: Your Name (TEAMID)\"\nexport MACOS_SIGN_INSTALLER=\"Developer ID Installer: Your Name (TEAMID)\"\nexport MACOS_NOTARIZE_APPLE_ID=\"your@email.com\"\nexport MACOS_NOTARIZE_TEAM_ID=\"ABCD123456\"\nexport MACOS_NOTARIZE_PASSWORD=\"xxxx-xxxx-xxxx-xxxx\"\n\n# Build the signed and notarized package\ncd build-aux/pkg-installer\nVERSION=2.26.0 ./build-pkg.sh\n\n# Verify the signature\npkgutil --check-signature ../../build-output/Telepresence.pkg\nspctl --assess --type install ../../build-output/Telepresence.pkg\n```\n"
  },
  {
    "path": "CODE-OF-CONDUCT.md",
    "content": "# Code of Conduct\n\nWe follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).\n\nPlease contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io)\nin order to report violations of the Code of Conduct."
  },
  {
    "path": "CODEOWNERS",
    "content": "# Default codeowners for the telepresenceio/telepresence repository\n* @telepresenceio/telepresence\n"
  },
  {
    "path": "DEPENDENCIES.md",
    "content": "The Go module \"github.com/telepresenceio/telepresence/v2\" incorporates the\nfollowing Free and Open Source software:\n\n    Name                                                                      Version                               License(s)\n    ----                                                                      -------                               ----------\n    the Go language standard library (\"std\")                                  v1.26                                 3-clause BSD license\n    dario.cat/mergo                                                           v1.0.2                                3-clause BSD license\n    github.com/Azure/go-ansiterm                                              v0.0.0-20250102033503-faa5f7b0171c    MIT license\n    github.com/BurntSushi/toml                                                v1.6.0                                MIT license\n    github.com/MakeNowJust/heredoc                                            v1.0.0                                MIT license\n    github.com/Masterminds/goutils                                            v1.1.1                                Apache License 2.0\n    github.com/Masterminds/semver/v3                                          v3.4.0                                MIT license\n    github.com/Masterminds/sprig/v3                                           v3.3.0                                MIT license\n    github.com/Masterminds/squirrel                                           v1.5.4                                MIT license\n    github.com/Microsoft/go-winio                                             v0.6.2                                MIT license\n    github.com/alexflint/go-filemutex                                         v1.3.0                                MIT license\n    github.com/asaskevich/govalidator                                         v0.0.0-20230301143203-a9d515a09cc2    MIT license\n    github.com/beorn7/perks                                                   v1.0.1                                MIT license\n    github.com/blang/semver/v4                                                v4.0.0                                MIT license\n    github.com/thallgren/env/v11 (modified from github.com/caarlos0/env/v11)  v11.0.0-20260107112108-5d5593a09332   MIT license\n    github.com/cenkalti/backoff/v4                                            v4.3.0                                MIT license\n    github.com/cespare/xxhash/v2                                              v2.3.0                                MIT license\n    github.com/chai2010/gettext-go                                            v1.0.3                                3-clause BSD license\n    github.com/clipperhouse/uax29/v2                                          v2.7.0                                MIT license\n    github.com/compose-spec/compose-go/v2                                     v2.10.1                               Apache License 2.0, MIT license\n    github.com/containerd/containerd                                          v1.7.30                               Apache License 2.0\n    github.com/containerd/errdefs                                             v1.0.0                                Apache License 2.0\n    github.com/containerd/errdefs/pkg                                         v0.3.0                                Apache License 2.0\n    github.com/containerd/log                                                 v0.1.0                                Apache License 2.0\n    github.com/containerd/platforms                                           v0.2.1                                Apache License 2.0\n    github.com/coreos/go-iptables                                             v0.8.0                                Apache License 2.0\n    github.com/cyphar/filepath-securejoin                                     v0.6.1                                3-clause BSD license, Mozilla Public License 2.0\n    github.com/datawire/argo-rollouts-go-client                               v0.0.0-20241216133646-cb1073556c99    Apache License 2.0\n    github.com/davecgh/go-spew                                                v1.1.2-0.20180830191138-d8f796af33cc  ISC license\n    github.com/distribution/reference                                         v0.6.0                                Apache License 2.0\n    github.com/docker/docker                                                  v28.5.2+incompatible                  Apache License 2.0\n    github.com/docker/go-connections                                          v0.6.0                                Apache License 2.0\n    github.com/docker/go-units                                                v0.5.0                                Apache License 2.0\n    github.com/emicklei/go-restful/v3                                         v3.13.0                               MIT license\n    github.com/evanphx/json-patch                                             v5.9.11+incompatible                  3-clause BSD license\n    github.com/exponent-io/jsonpath                                           v0.0.0-20210407135951-1de76d718b3f    MIT license\n    github.com/fatih/camelcase                                                v1.0.0                                MIT license\n    github.com/fatih/color                                                    v1.18.0                               MIT license\n    github.com/fclairamb/ftpserverlib                                         v0.30.0                               MIT license\n    github.com/felixge/httpsnoop                                              v1.0.4                                MIT license\n    github.com/fsnotify/fsnotify                                              v1.9.0                                3-clause BSD license\n    github.com/fxamacker/cbor/v2                                              v2.9.0                                MIT license\n    github.com/go-errors/errors                                               v1.5.1                                MIT license\n    github.com/go-gorp/gorp/v3                                                v3.1.0                                MIT license\n    github.com/go-json-experiment/json                                        v0.0.0-20260214004413-d219187c3433    3-clause BSD license\n    github.com/go-logr/logr                                                   v1.4.3                                Apache License 2.0\n    github.com/go-logr/stdr                                                   v1.2.2                                Apache License 2.0\n    github.com/go-openapi/jsonpointer                                         v0.22.4                               Apache License 2.0\n    github.com/go-openapi/jsonreference                                       v0.21.4                               Apache License 2.0\n    github.com/go-openapi/swag                                                v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/cmdutils                                       v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/conv                                           v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/fileutils                                      v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/jsonname                                       v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/jsonutils                                      v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/loading                                        v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/mangling                                       v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/netutils                                       v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/stringutils                                    v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/typeutils                                      v0.25.4                               Apache License 2.0\n    github.com/go-openapi/swag/yamlutils                                      v0.25.4                               Apache License 2.0\n    github.com/go-viper/mapstructure/v2                                       v2.5.0                                MIT license\n    github.com/gobwas/glob                                                    v0.2.3                                MIT license\n    github.com/godbus/dbus/v5                                                 v5.2.2                                2-clause BSD license\n    github.com/gogo/protobuf                                                  v1.3.2                                3-clause BSD license\n    github.com/google/btree                                                   v1.1.3                                Apache License 2.0\n    github.com/google/gnostic-models                                          v0.7.1                                Apache License 2.0\n    github.com/google/go-cmp                                                  v0.7.0                                3-clause BSD license\n    github.com/google/jsonschema-go                                           v0.4.2                                MIT license\n    github.com/google/uuid                                                    v1.6.0                                3-clause BSD license\n    github.com/gorilla/websocket                                              v1.5.4-0.20250319132907-e064f32e3674  2-clause BSD license\n    github.com/gosuri/uitable                                                 v0.0.4                                MIT license\n    github.com/gregjones/httpcache                                            v0.0.0-20190611155906-901d90724c79    MIT license\n    github.com/grpc-ecosystem/go-grpc-middleware/v2                           v2.3.3                                Apache License 2.0\n    github.com/hashicorp/errwrap                                              v1.1.0                                Mozilla Public License 2.0\n    github.com/hashicorp/go-multierror                                        v1.1.1                                Mozilla Public License 2.0\n    github.com/hectane/go-acl                                                 v0.0.0-20230122075934-ca0b05cb1adb    MIT license\n    github.com/huandu/xstrings                                                v1.5.0                                MIT license\n    github.com/inconshreveable/mousetrap                                      v1.1.0                                Apache License 2.0\n    github.com/jlaffaye/ftp                                                   v0.2.0                                ISC license\n    github.com/jmoiron/sqlx                                                   v1.4.0                                MIT license\n    github.com/json-iterator/go                                               v1.1.12                               MIT license\n    github.com/klauspost/compress                                             v1.18.4                               3-clause BSD license, Apache License 2.0, MIT license\n    github.com/kr/fs                                                          v0.1.0                                3-clause BSD license\n    github.com/lann/builder                                                   v0.0.0-20180802200727-47ae307949d0    MIT license\n    github.com/lann/ps                                                        v0.0.0-20150810152359-62de8c46ede0    MIT license\n    github.com/lib/pq                                                         v1.11.2                               MIT license\n    github.com/liggitt/tabwriter                                              v0.0.0-20181228230101-89fcab3d43de    3-clause BSD license\n    github.com/mattn/go-colorable                                             v0.1.14                               MIT license\n    github.com/mattn/go-isatty                                                v0.0.20                               MIT license\n    github.com/mattn/go-runewidth                                             v0.0.20                               MIT license\n    github.com/mattn/go-shellwords                                            v1.0.12                               MIT license\n    github.com/miekg/dns                                                      v1.1.72                               3-clause BSD license\n    github.com/mitchellh/copystructure                                        v1.2.0                                MIT license\n    github.com/mitchellh/go-wordwrap                                          v1.0.1                                MIT license\n    github.com/mitchellh/reflectwalk                                          v1.0.2                                MIT license\n    github.com/moby/docker-image-spec                                         v1.3.1                                Apache License 2.0\n    github.com/moby/spdystream                                                v0.5.0                                Apache License 2.0\n    github.com/moby/term                                                      v0.5.2                                Apache License 2.0\n    github.com/modelcontextprotocol/go-sdk                                    v1.4.0                                Apache License 2.0, MIT license\n    github.com/modern-go/concurrent                                           v0.0.0-20180306012644-bacd9c7ef1dd    Apache License 2.0\n    github.com/modern-go/reflect2                                             v1.0.3-0.20250322232337-35a7c28c31ee  Apache License 2.0\n    github.com/monochromegane/go-gitignore                                    v0.0.0-20200626010858-205db1a8cc00    MIT license\n    github.com/morikuni/aec                                                   v1.1.0                                MIT license\n    github.com/munnerz/goautoneg                                              v0.0.0-20191010083416-a7dc8b61c822    3-clause BSD license\n    github.com/mxk/go-flowrate                                                v0.0.0-20140419014527-cca7078d478f    3-clause BSD license\n    github.com/njayp/ophis                                                    v1.1.4                                Apache License 2.0\n    github.com/opencontainers/go-digest                                       v1.0.0                                Apache License 2.0\n    github.com/opencontainers/image-spec                                      v1.1.1                                Apache License 2.0\n    github.com/peterbourgon/diskv                                             v2.0.1+incompatible                   MIT license\n    github.com/pkg/browser                                                    v0.0.0-20240102092130-5ac0b6a4141c    2-clause BSD license\n    github.com/pkg/errors                                                     v0.9.1                                2-clause BSD license\n    github.com/pkg/sftp                                                       v1.13.10                              2-clause BSD license\n    github.com/pmezard/go-difflib                                             v1.0.1-0.20181226105442-5d4384ee4fb2  3-clause BSD license\n    github.com/prometheus/client_golang                                       v1.23.2                               3-clause BSD license, Apache License 2.0\n    github.com/prometheus/client_model                                        v0.6.2                                Apache License 2.0\n    github.com/prometheus/common                                              v0.67.5                               Apache License 2.0\n    github.com/prometheus/procfs                                              v0.20.0                               Apache License 2.0\n    github.com/puzpuzpuz/xsync/v4                                             v4.4.0                                Apache License 2.0\n    github.com/rogpeppe/go-internal                                           v1.14.1                               3-clause BSD license\n    github.com/rubenv/sql-migrate                                             v1.8.1                                MIT license\n    github.com/russross/blackfriday/v2                                        v2.1.0                                2-clause BSD license\n    github.com/sabhiram/go-gitignore                                          v0.0.0-20210923224102-525f6e181f06    MIT license\n    github.com/santhosh-tekuri/jsonschema/v6                                  v6.0.2                                Apache License 2.0\n    github.com/segmentio/asm                                                  v1.2.1                                MIT No Attribution license\n    github.com/segmentio/encoding                                             v0.5.3                                MIT license\n    github.com/shopspring/decimal                                             v1.4.0                                MIT license\n    github.com/sirupsen/logrus                                                v1.9.4                                MIT license\n    github.com/spf13/afero                                                    v1.15.0                               Apache License 2.0\n    github.com/spf13/cast                                                     v1.10.0                               MIT license\n    github.com/spf13/cobra                                                    v1.10.2                               Apache License 2.0\n    github.com/spf13/pflag                                                    v1.0.10                               3-clause BSD license\n    github.com/stretchr/testify                                               v1.11.1                               MIT license\n    github.com/telepresenceio/clog                                            v0.0.0-20260114221933-287514cf9831    Apache License 2.0\n    github.com/telepresenceio/go-ftpserver                                    v1.2.1                                Apache License 2.0\n    github.com/telepresenceio/go-fuseftp                                      v1.0.1                                Apache License 2.0\n    github.com/telepresenceio/go-fuseftp/rpc                                  v1.0.1                                Apache License 2.0\n    github.com/telepresenceio/telepresence/cmd/cobraparser/v2                 (modified)                            Apache License 2.0\n    github.com/telepresenceio/telepresence/rpc/v2                             (modified)                            Apache License 2.0\n    github.com/vishvananda/netlink                                            v1.3.1                                Apache License 2.0\n    github.com/vishvananda/netns                                              v0.0.5                                Apache License 2.0\n    github.com/winfsp/cgofuse                                                 v1.6.0                                MIT license\n    github.com/x448/float16                                                   v0.8.4                                MIT license\n    github.com/xhit/go-str2duration/v2                                        v2.1.0                                3-clause BSD license\n    github.com/xlab/treeprint                                                 v1.2.0                                MIT license\n    github.com/yosida95/uritemplate/v3                                        v3.0.2                                3-clause BSD license\n    go.opentelemetry.io/auto/sdk                                              v1.2.1                                Apache License 2.0\n    go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp             v0.65.0                               3-clause BSD license, Apache License 2.0\n    go.opentelemetry.io/otel                                                  v1.40.0                               3-clause BSD license, Apache License 2.0\n    go.opentelemetry.io/otel/metric                                           v1.40.0                               3-clause BSD license, Apache License 2.0\n    go.opentelemetry.io/otel/trace                                            v1.40.0                               3-clause BSD license, Apache License 2.0\n    go.yaml.in/yaml/v2                                                        v2.4.3                                Apache License 2.0, MIT license\n    go.yaml.in/yaml/v3                                                        v3.0.4                                Apache License 2.0, MIT license\n    go.yaml.in/yaml/v4                                                        v4.0.0-rc.4                           Apache License 2.0, MIT license\n    golang.org/x/crypto                                                       v0.48.0                               3-clause BSD license\n    golang.org/x/exp                                                          v0.0.0-20260218203240-3dfff04db8fa    3-clause BSD license\n    golang.org/x/mod                                                          v0.33.0                               3-clause BSD license\n    golang.org/x/net                                                          v0.51.0                               3-clause BSD license\n    golang.org/x/oauth2                                                       v0.35.0                               3-clause BSD license\n    golang.org/x/sync                                                         v0.19.0                               3-clause BSD license\n    golang.org/x/sys                                                          v0.41.0                               3-clause BSD license\n    golang.org/x/term                                                         v0.40.0                               3-clause BSD license\n    golang.org/x/text                                                         v0.34.0                               3-clause BSD license\n    golang.org/x/time                                                         v0.14.0                               3-clause BSD license\n    golang.org/x/tools                                                        v0.42.0                               3-clause BSD license\n    golang.zx2c4.com/wintun                                                   v0.0.0-20230126152724-0fa3db229ce2    MIT license\n    golang.zx2c4.com/wireguard                                                v0.0.0-20250521234502-f333402bd9cb    MIT license\n    golang.zx2c4.com/wireguard/windows                                        v0.5.3                                MIT license\n    google.golang.org/genproto/googleapis/rpc                                 v0.0.0-20260226221140-a57be14db171    Apache License 2.0\n    google.golang.org/grpc                                                    v1.79.1                               Apache License 2.0\n    google.golang.org/protobuf                                                v1.36.11                              3-clause BSD license\n    gopkg.in/evanphx/json-patch.v4                                            v4.13.0                               3-clause BSD license\n    gopkg.in/inf.v0                                                           v0.9.1                                3-clause BSD license\n    gopkg.in/yaml.v3                                                          v3.0.1                                Apache License 2.0, MIT license\n    gvisor.dev/gvisor                                                         v0.0.0-20260224225140-573d5e7127a8    3-clause BSD license, Apache License 2.0, MIT license\n    helm.sh/helm/v3                                                           v3.20.0                               Apache License 2.0\n    k8s.io/api                                                                v0.35.2                               Apache License 2.0\n    k8s.io/apiextensions-apiserver                                            v0.35.2                               Apache License 2.0\n    k8s.io/apimachinery                                                       v0.35.2                               3-clause BSD license, Apache License 2.0\n    k8s.io/apiserver                                                          v0.35.2                               Apache License 2.0\n    k8s.io/cli-runtime                                                        v0.35.2                               Apache License 2.0\n    k8s.io/client-go                                                          v0.35.2                               3-clause BSD license, Apache License 2.0\n    k8s.io/component-base                                                     v0.35.2                               Apache License 2.0\n    k8s.io/component-helpers                                                  v0.35.2                               Apache License 2.0\n    k8s.io/klog/v2                                                            v2.130.1                              Apache License 2.0\n    k8s.io/kube-openapi                                                       v0.0.0-20260127142750-a19766b6e2d4    3-clause BSD license, Apache License 2.0\n    k8s.io/kubectl                                                            v0.35.2                               Apache License 2.0\n    k8s.io/utils                                                              v0.0.0-20260210185600-b8788abfbbc2    3-clause BSD license, Apache License 2.0\n    oras.land/oras-go/v2                                                      v2.6.0                                Apache License 2.0\n    sigs.k8s.io/json                                                          v0.0.0-20250730193827-2d320260d730    3-clause BSD license, Apache License 2.0\n    sigs.k8s.io/kustomize/api                                                 v0.21.1                               Apache License 2.0\n    sigs.k8s.io/kustomize/kyaml                                               v0.21.1                               Apache License 2.0\n    sigs.k8s.io/randfill                                                      v1.0.0                                Apache License 2.0\n    sigs.k8s.io/structured-merge-diff/v6                                      v6.3.2                                Apache License 2.0\n    sigs.k8s.io/yaml                                                          v1.6.0                                3-clause BSD license, Apache License 2.0, MIT license\n"
  },
  {
    "path": "DEPENDENCY_LICENSES.md",
    "content": "Telepresence CLI incorporates Free and Open Source software under the following licenses:\n\n* [2-clause BSD license](https://opensource.org/licenses/BSD-2-Clause)\n* [3-clause BSD license](https://opensource.org/licenses/BSD-3-Clause)\n* [Apache License 2.0](https://opensource.org/licenses/Apache-2.0)\n* [ISC license](https://opensource.org/licenses/ISC)\n* [MIT No Attribution license](https://spdx.org/licenses/MIT-0.html)\n* [MIT license](https://opensource.org/licenses/MIT)\n* [Mozilla Public License 2.0](https://opensource.org/licenses/MPL-2.0)\n"
  },
  {
    "path": "GOVERNANCE-maintainer.md",
    "content": "# Telepresence Project Governance\n\nThe goal of the Telepresence project is to accelerate the \"developer inner loop\" for cloud-native application\ndevelopment on Kubernetes.\n\nTelepresence achieves this by creating a dynamic network bridge between a local development environment (e.g., a laptop)\nand a remote Kubernetes cluster.\n\nThis governance explains how the project is run.\n\n- [Values](#values)\n- [Maintainers](#maintainers)\n- [Becoming a Maintainer](#becoming-a-maintainer)\n- [Meetings](#meetings)\n- [CNCF Resources](#cncf-resources)\n- [Code of Conduct Enforcement](#code-of-conduct)\n- [Security Response Team](#security-response-team)\n- [Voting](#voting)\n- [Modifications](#modifying-this-charter)\n\n## Values\n\nThe Telepresence project and its leadership embrace the following values:\n\n* Openness: Communication and decision-making happens in the open and is discoverable for future\n  reference. As much as possible, all discussions and work take place in public\n  forums and open repositories.\n\n* Fairness: All stakeholders have the opportunity to provide feedback and submit\n  contributions, which will be considered on their merits.\n\n* Community over Product or Company: Sustaining and growing our community takes\n  priority over shipping code or sponsors' organizational goals.  Each\n  contributor participates in the project as an individual.\n\n* Inclusivity: We innovate through different perspectives and skill sets, which\n  can only be accomplished in a welcoming and respectful environment.\n\n* Participation: Responsibilities within the project are earned through\n  participation, and there is a clear path up the contributor ladder into leadership\n  positions.\n\n## Maintainers\n\nTelepresence Maintainers have write access to the [project GitHub repository](https://github.com/telepresenceio/telepresence).\nThey can merge their own patches or patches from others. The current maintainers\ncan be found in [MAINTAINERS.md](./MAINTAINERS.md).  Maintainers collectively manage the project's\nresources and contributors.\n\nThis privilege is granted with some expectation of responsibility: maintainers\nare people who care about the Telepresence project and want to help it grow and\nimprove. A maintainer is not just someone who can make changes, but someone who\nhas demonstrated their ability to collaborate with the team, get the most\nknowledgeable people to review code and docs, contribute high-quality code, and\nfollow through to fix issues (in code or tests).\n\nA maintainer is a contributor to the project's success and a citizen helping\nthe project succeed.\n\nThe collective team of all Maintainers is known as the Maintainer Council, which\nis the governing body for the project.\n\n### Maintainer responsibilities\n\n* Monitor email aliases.\n* Monitor Slack (delayed response is perfectly acceptable).\n* Triage GitHub issues and perform pull request reviews for other maintainers and the community.\n* Make sure that ongoing PRs are moving forward at the right pace or closing them.\n* In general continue to be willing to spend at least 20% of one's time working on Telepresence (~1 business day/week).\n\n### Becoming a Maintainer\n\n* Express interest to the current maintainers (see [MAINTAINERS.md](MAINTAINERS.md)) that your organization is\ninterested in becoming a maintainer. Becoming a maintainer generally means that you are going to be spending\nsubstantial time on Telepresence for the foreseeable future.\n* We will expect you to start contributing increasingly complicated PRs, under the guidance of the existing maintainers.\n* We may ask you to do some PRs from our backlog.\n* As you gain experience with the code base and our standards, we will ask you to do code reviews for incoming PRs.\nAll maintainers are expected to shoulder a proportional share of community reviews.\n* After a period of approximately 2-3 months of working together and making sure we see eye to eye, the existing\nmaintainers will confer and decide whether to grant maintainer status or not.\nWe make no guarantees on the length of time this will take, but 2-3 months is the goal.\n\nA new Maintainer can apply by sending a message in our [OSS Slack workspace](https://communityinviter.com/apps/cloud-native/cncf),\nin the [#telepresence-oss](https://cloud-native.slack.com/archives/C06B36KJ85P) channel.\n\nA simple majority vote of existing Maintainers approves the application.\n\nMaintainers nominations will be evaluated without prejudice\nto employer or demographics.\n\nMaintainers who are selected will be granted the necessary GitHub rights.\n\n### Removing a Maintainer\n\nMaintainers may resign at any time if they feel that they will not be able to\ncontinue fulfilling their project duties.\n\nMaintainers may also be removed after being inactive, failure to fulfill their\nMaintainer responsibilities, violating the Code of Conduct, or other reasons.\nInactivity is defined as a period of very low or no activity in the project\nfor a year or more, with no definite schedule to return to full Maintainer\nactivity.\n\nA Maintainer may be removed at any time by a 2/3 vote of the remaining maintainers.\n\nDepending on the reason for removal, a Maintainer may be converted to Emeritus\nstatus. Emeritus Maintainers will still be consulted on some project matters,\nand can be rapidly returned to Maintainer status if their availability changes.\n\n## Meetings\n\nMaintainers meet on demand to discuss project matters, security reports,\nor Code of Conduct violations. Any Maintainer may schedule a meeting as needed.\nAll current Maintainers must be invited, except for any Maintainer who is\naccused of a CoC violation.\n\n## CNCF Resources\n\nAny Maintainer may suggest a request for CNCF resources, either in the\n[#telepresence-dev](https://datawire-oss.slack.com/archives/CC5D1UTTN) in slack, or during a\nmeeting.  A simple majority of Maintainers approves the request.  The Maintainers\nmay also choose to delegate working with the CNCF to non-Maintainer community\nmembers, who will then be added to the [CNCF's Maintainer List](https://github.com/cncf/foundation/blob/main/project-maintainers.csv)\nfor that purpose.\n\n## Code of Conduct\n\n[Code of Conduct](./code-of-conduct.md)\nviolations by community members will be discussed and resolved\non the [private slack channel](https://datawire-oss.slack.com/archives/C061Q45SU4F).  If a Maintainer is directly involved\nin the report, the Maintainers will instead designate two Maintainers to work\nwith the CNCF Code of Conduct Committee in resolving it.\n\n## Security Response Team\n\nThe Maintainers will appoint a Security Response Team to handle security reports.\nThis committee may simply consist of the Maintainer Council themselves.  If this\nresponsibility is delegated, the Maintainers will appoint a team of at least two\ncontributors to handle it.  The Maintainers will review who is assigned to this\nat least once a year.\n\nThe Security Response Team is responsible for handling all reports of security\nholes and breaches according to the [security policy](./SECURITY.md).\n\n## Voting\n\nIn general, we prefer that technical issues and maintainer membership are amicably worked out between the persons\ninvolved. If a dispute cannot be decided independently, the maintainers can be called in to decide an issue. If the\nmaintainers themselves cannot decide an issue, the issue will be resolved by voting.\nThe voting process is a simple majority in which each maintainer receives one vote.\n\nA vote can be taken on [#telepresence-dev](https://datawire-oss.slack.com/archives/CC5D1UTTN) or\n[#telepresence-dev-private](https://datawire-oss.slack.com/archives/C061Q45SU4F) for security or conduct matters.\n\nAny Maintainer may demand a vote be taken.\n\nMost votes require a simple majority of all Maintainers to succeed, except where\notherwise noted.  Two-thirds majority votes mean at least two-thirds of all\nexisting maintainers.\n\n## Modifying this Charter\n\nChanges to this Governance and its supporting documents may be approved by\na 2/3 vote of the Maintainers.\n\n## Adding new projects to the Telepresence GitHub organization\n\nNew projects will be added to the Telepresence organization via GitHub issue discussion in one of the existing projects\nin the organization. Once sufficient discussion has taken place (~3-5 business days but depending on the volume\nof conversation), the maintainers of *the project where the issue was opened* (since different projects in the\norganization may have different maintainers) will decide whether the new project should be added. See the section\nabove on voting if the maintainers cannot easily decide.\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\n"
  },
  {
    "path": "MAINTAINERS.md",
    "content": "# Telepresence Maintainers\n\n[GOVERNANCE.md](GOVERNANCE.md) describes governance guidelines and\nmaintainer responsibilities.\n\n## Maintainers\n\nMaintainers are listed in alphabetical order.\n\n| Maintainer      | GitHub ID                                 | Affiliation |\n|-----------------|-------------------------------------------|-------------|\n| Blazej Gruszka  | [bgruszka](https://github.com/bgruszka)   | Displate    |\n| Nick Powell     | [njayp](https://github.com/njayp)         |             |\n| Thomas Hallgren | [thallgren](https://github.com/thallgren) |             |\n\n## Maintainers Emeriti\n\n* Abhay Saxena ([ark3](https://github.com/ark3))\n* Donny Yung ([donnyyung](https://github.com/donnyyung))\n* Guillaume Veschambre ([shepz](https://github.com/shepz))\n* Jakub Rożek [P0lip](https://github.com/P0lip)\n* Jose Cortes ([josecv](https://github.com/josecv))\n* Kévin Lambert ([knlambert](https://github.com/knlambert))\n* Luke Shumaker ([LukeShu](https://github.com/LukeShu))\n* Rafael Schloming ([rhs](https://github.com/rhs))\n* Raphael Reyna ([raphaelreyna](https://github.com/raphaelreyna))\n* Richard Li ([richarddli](https://github.com/richarddli))\n* Sarabraj Singh ([sarabrajsingh](https://github.com/sarabrajsingh))\n"
  },
  {
    "path": "Makefile",
    "content": "# Copyright 2020-2021 Datawire.  All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nTELEPRESENCE_REGISTRY ?= ghcr.io/telepresenceio\nTELEPRESENCE_VERSION ?= $(shell unset GOOS GOARCH; go run ./build-aux/genversion)\n# Ensure that the variable is fully expanded. We don't want to call genversion repeatedly\n# as it may produce different results every time.\nTELEPRESENCE_VERSION := ${TELEPRESENCE_VERSION}\n\nSHELL:=$(shell which bash)\n\n$(if $(filter v2.%,$(TELEPRESENCE_VERSION)),\\\n  $(info [make] TELEPRESENCE_VERSION=$(TELEPRESENCE_VERSION)),\\\n  $(error TELEPRESENCE_VERSION variable is invalid: It must be a v2.* string, but is '$(TELEPRESENCE_VERSION)'))\nexport TELEPRESENCE_VERSION\n\n.DEFAULT_GOAL = help\n\ninclude build-aux/prelude.mk\ninclude build-aux/tools.mk\ninclude build-aux/main.mk\ninclude build-aux/maintenance.mk\n"
  },
  {
    "path": "README.md",
    "content": "# Telepresence: Fast, Local Development for Kubernetes\n\n[<img src=\"https://raw.githubusercontent.com/telepresenceio/telepresence.io/master/src/assets/images/telepresence-edgy.svg\" width=\"80\"/>](https://telepresence.io)\n\n[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/telepresence-oss)](https://artifacthub.io/packages/search?repo=telepresence-oss) [![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Telepresence%20Guru-006BFF)](https://gurubase.io/g/telepresence)\n\nTelepresence is a [CNCF](https://www.cncf.io/) project that connects your local workstation to a remote Kubernetes cluster, letting you run services locally while accessing cluster resources. It enables fast local development without the container build/push/deploy cycle.\n\n## Key Features\n\n- **Local Development** - Run services on your workstation using your favorite IDE, debugger, and tools\n- **Cluster Access** - Your local machine can reach cluster services as if it were inside the cluster\n- **Traffic Interception** - Redirect traffic from cluster services to your local machine for debugging\n- **Fast Iteration** - No waiting for container builds or deployments\n\n## Getting Started\n\n- [Quick Start Guide](https://telepresence.io/docs/quick-start) - Get up and running in minutes\n- [Installation](https://telepresence.io/docs/install/client) - Install the Telepresence client\n- [Documentation](https://telepresence.io/docs/) - Full documentation\n\n## How It Works\n\nWhen Telepresence connects to a cluster, it creates a virtual network interface on your workstation and routes traffic through a Traffic Manager deployed in the cluster. This allows your local services to communicate with cluster resources and optionally intercept traffic destined for cluster workloads.\n\n## Community\n\n- [CNCF Slack](https://communityinviter.com/apps/cloud-native/cncf) - Join [#telepresence-oss](https://cloud-native.slack.com/archives/C06B36KJ85P)\n- [Troubleshooting](https://telepresence.io/docs/troubleshooting/) - Common issues and solutions\n\n## Contributing\n\nSee [CLAUDE.md](CLAUDE.md) for build instructions, architecture overview, and development guidelines.\n\n## License\n\nTelepresence is licensed under the [Apache License 2.0](LICENSE).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nSecurity updates will be provided for the latest 2.x release.\n\n\n### How do we handle vulnerabilities\n\n#### User reports\n\nIf you discover any security vulnerabilities, please follow these guidelines:\n\n- Email your findings to [secalert@tada.se](secalert@tada.se).\n- Provide sufficient details, including steps to reproduce the vulnerability.\n- Do not publicly disclose the issue until we have had a chance to address it.\n\n#### Dependabot\n\nWe run dependabot against our repo. We also have it create PRs with the updates. \n\nOne of the maintainers responsibilities is to review these PRs, make any necessary updates, \nand merge them in so that they go out in our next set of releases.\n\n#### Keeping Go updated\n\nWe're set up to receive embargoed security announcements for Golang. When it happens, \nwe create a new security incident, evaluate if we're impacted, and release a hotfix as soon as possible.\n\n"
  },
  {
    "path": "build-aux/INSTALLERS.md",
    "content": "# Telepresence Installers Specification\n\nThis document describes the platform-specific installers that bundle Telepresence with a system service for the root daemon.\n\n## Overview\n\nTelepresence provides two installation methods:\n1. **Standalone binaries** - Manual installation, root daemon runs on-demand with elevated privileges\n2. **Platform installers** - Include root daemon as a system service, eliminating repeated privilege elevation\n\nAll installers are built and published via `.github/workflows/release.yaml`.\n\n## Windows Installer (WiX)\n\n**Location:** `build-aux/wix-installer/`\n\n**Output:** `telepresence-windows-amd64-setup.exe` (WiX bundle/bootstrapper)\n\n**Architecture:** amd64 only (WinFSP and SSHFS-Win installers lack arm64 versions)\n\n### Key Files\n- `Makefile` - Build orchestration, downloads binary if not present locally\n- `TeleProduct.wxs` - MSI product definition\n- `TeleBundle.wxs` - Bundle/bootstrapper definition\n- `variables.wxi` - Version and path variables\n\n### CI Build Steps\n```yaml\n- name: Install WiX Toolset\n  run: |\n    dotnet tool install --global wix\n    wix extension add -g WixToolset.BootstrapperApplications.wixext\n    wix extension add -g WixToolset.UI.wixext\n    wix extension add -g WixToolset.Util.wixext\n- name: Build WiX Installer\n  run: make bundle ARCH=amd64\n```\n\n### Service Details\nThe Windows installer registers Telepresence and configures PATH. The root daemon service management is handled differently on Windows (not a persistent service like Unix platforms).\n\n---\n\n## macOS Installer (pkg)\n\n**Location:** `build-aux/pkg-installer/`\n\n**Output:** `telepresence-darwin-{amd64,arm64}.pkg`\n\n**Architecture:** amd64 and arm64\n\n### Key Files\n- `build-pkg.sh` - Build script using `pkgbuild` and `productbuild`\n- `io.telepresence.rootd.plist` - launchd daemon configuration\n- `scripts/postinstall` - Post-installation script\n- `scripts/preinstall` - Pre-installation script\n- `distribution.xml` - Installer UI/flow definition\n\n### CI Build Steps\n```yaml\n- name: Build macOS Installer\n  run: |\n    cd build-aux/pkg-installer\n    VERSION=\"${TELEPRESENCE_VERSION#v}\" ARCH=${{ matrix.arch }} ./build-pkg.sh\n```\n\n### launchd Service (`io.telepresence.rootd.plist`)\n```xml\n<key>ProgramArguments</key>\n<array>\n  <string>/usr/local/bin/telepresence</string>\n  <string>rootd</string>\n  <string>--logfile</string>  <string>std</string>\n  <string>--config</string>   <string>/Library/Application Support/telepresence/config.yml</string>\n  <string>--address</string>  <string>:4037</string>\n  <string>--managed</string>\n</array>\n<key>RunAtLoad</key><true/>\n<key>KeepAlive</key><true/>\n```\n\n**Log location:** `/var/log/telepresence-rootd.log`\n**Working directory:** `/Library/Caches/telepresence`\n\n---\n\n## Linux Installer (nfpm)\n\n**Location:** `build-aux/systemd-installer/`\n\n**Output:**\n- `telepresence-{version}-linux-{amd64,arm64}.deb` (Debian/Ubuntu)\n- `telepresence-{version}-linux-{amd64,arm64}.rpm` (Fedora/RHEL)\n\n**Architecture:** amd64 and arm64\n\n### Key Files\n- `build-packages.sh` - Build script, handles version parsing and nfpm invocation\n- `nfpm.yaml.in` - nfpm configuration template (uses envsubst for variable expansion)\n- `telepresence-rootd.service` - systemd unit file\n- `postinstall.sh` - Post-installation script (creates directories, enables service)\n- `preremove.sh` - Pre-removal script (stops and disables service)\n- `postremove.sh` - Post-removal script (cleans up directories)\n\n### CI Build Steps\n```yaml\n- name: Install nfpm\n  run: |\n    curl -sfL \"https://github.com/goreleaser/nfpm/releases/download/v2.44.1/nfpm_2.44.1_Linux_x86_64.tar.gz\" | tar xz -C /tmp\n    sudo mv /tmp/nfpm /usr/local/bin/nfpm\n- name: Build Linux Packages\n  run: |\n    cd build-aux/systemd-installer\n    VERSION=\"${TELEPRESENCE_VERSION#v}\" ARCH=${{ matrix.arch }} ./build-packages.sh\n```\n\n### Version Handling\nRPM doesn't allow hyphens in version numbers. The build script splits versions:\n- `2.27.0` → version=`2.27.0`, release=`1`\n- `2.27.0-rc.1` → version=`2.27.0`, release=`rc.1`\n- `2.27.0-test.5` → version=`2.27.0`, release=`test.5`\n\n### nfpm.yaml.in Template\n```yaml\nname: telepresence\narch: ${ARCH}\nplatform: linux\nversion: ${PKG_VERSION}\nrelease: ${PKG_RELEASE}\nmaintainer: Telepresence Maintainers <telepresence@datawire.io>\ndescription: Fast local Kubernetes development\nvendor: Ambassador Labs\nhomepage: https://www.telepresence.io/\nlicense: Apache-2.0\n\ncontents:\n  - src: ${BINDIR}/telepresence\n    dst: /usr/local/bin/telepresence\n  - src: telepresence-rootd.service\n    dst: /usr/lib/systemd/system/telepresence-rootd.service\n\nscripts:\n  postinstall: postinstall.sh\n  preremove: preremove.sh\n  postremove: postremove.sh\n```\n\n### systemd Service (`telepresence-rootd.service`)\n```ini\n[Service]\nType=simple\nExecStart=/usr/local/bin/telepresence rootd \\\n    --logfile managed \\\n    --config /etc/telepresence/config.yml \\\n    --address :4037 \\\n    --managed\n\nUser=root\nGroup=root\nWorkingDirectory=/var/cache/telepresence\nRuntimeDirectory=telepresence\nRestart=always\nRestartSec=3\n\n# Logging\nStandardOutput=append:/var/log/telepresence/rootd.log\nStandardError=append:/var/log/telepresence/rootd.log\n\n# Security hardening\nNoNewPrivileges=yes\nProtectSystem=strict\nReadWritePaths=/var/cache/telepresence /var/log/telepresence\nProtectHome=read-only\nPrivateTmp=yes\n```\n\n### Directory Structure\n| Path | Permissions | Purpose |\n|------|-------------|---------|\n| `/var/cache/telepresence` | 755 | Working directory |\n| `/var/cache/telepresence/rootd` | 755 | Root daemon cache |\n| `/var/log/telepresence` | 755 | Log directory |\n| `/etc/telepresence` | 755 | Configuration |\n\n### Post-install Behavior\n- Creates required directories with 755 permissions\n- Runs `systemctl daemon-reload`\n- Enables service (`systemctl enable telepresence-rootd`)\n- Does NOT start service (user decides when to start)\n\n---\n\n## Root Daemon Communication\n\nAll platforms use TCP for client-daemon communication:\n- **Address:** `:4037` (localhost port 4037)\n- **Flag:** `--address :4037`\n\nThe `--managed` flag indicates the daemon is running as a system service.\n\n---\n\n## Release Notes Structure\n\nThe GitHub release body separates installer types:\n\n**Installers (with root daemon as a system service):**\n- Linux .deb/.rpm\n- macOS .pkg\n- Windows setup.exe\n\n**Standalone Binaries:**\n- Linux/macOS executables\n- Windows .zip\n"
  },
  {
    "path": "build-aux/admission_controller_tls/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\tcryptorand \"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/sha1\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/base64\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"math/big\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\n// The program creates the crt.pem, key.pem, and ca.pem needed when\n// setting up the mutator webhook for agent auto-injection.\nfunc main() {\n\tif len(os.Args) != 3 {\n\t\tfmt.Fprintf(os.Stderr, \"usage: %s <manager-namespace> <directory>\", os.Args[0])\n\t\tos.Exit(1)\n\t}\n\tif err := generateKeys(os.Args[1], os.Args[2]); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n}\n\nfunc generateKeys(mgrNamespace, dir string) error {\n\terr := os.MkdirAll(dir, 0o777)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create directory %q: %w\", dir, err)\n\t}\n\tcrtPem, keyPem, caPem, err := generateKeyTriplet(mgrNamespace)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif err = writeFile(dir, \"ca.pem\", caPem); err != nil {\n\t\treturn err\n\t}\n\n\tif err = writeFile(dir, \"crt.pem\", crtPem); err != nil {\n\t\treturn err\n\t}\n\treturn writeFile(dir, \"key.pem\", keyPem)\n}\n\n// writeFile writes the file verbatim and as base64 encoded in the given directory.\nfunc writeFile(dir, file string, data []byte) error {\n\tfilePath := filepath.Join(dir, file)\n\tf, err := os.Create(filePath)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create file %q, %w\", filePath, err)\n\t}\n\tdefer f.Close()\n\n\tif _, err = f.Write(data); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to file %q, %w\", filePath, err)\n\t}\n\n\tfilePath64 := filePath + \".base64\"\n\tf64, err := os.Create(filePath64)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create file %q, %w\", filePath64, err)\n\t}\n\tdefer f64.Close()\n\n\tenc := base64.NewEncoder(base64.StdEncoding, f64)\n\tdefer enc.Close()\n\tif _, err = enc.Write(data); err != nil {\n\t\treturn fmt.Errorf(\"failed to write to file %q, %w\", filePath64, err)\n\t}\n\treturn nil\n}\n\n// generateKeyTriplet creates the crt.pem, key.pem, and ca.pem needed when\n// setting up the mutator webhook for agent auto-injection.\nfunc generateKeyTriplet(mgrNamespace string) (crtPem, keyPem, caPem []byte, err error) {\n\tcaCert := &x509.Certificate{\n\t\tSerialNumber: big.NewInt(0xefecab0),\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"telepresence.io\"},\n\t\t},\n\t\tNotBefore:             time.Now(),\n\t\tNotAfter:              time.Now().AddDate(1, 0, 0),\n\t\tIsCA:                  true,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,\n\t\tBasicConstraintsValid: true,\n\t}\n\n\tcaPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096)\n\tif err != nil {\n\t\treturn nil, nil, nil, fmt.Errorf(\"failed to generate CA private key: %w\", err)\n\t}\n\tcaBytes, err := x509.CreateCertificate(cryptorand.Reader, caCert, caCert, &caPrivKey.PublicKey, caPrivKey)\n\tif err != nil {\n\t\treturn nil, nil, nil, fmt.Errorf(\"failed to generate CA certificate: %w\", err)\n\t}\n\tif caPem, err = ToPEM(\"ca.pem\", \"CERTIFICATE\", caBytes); err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\n\tcommonName := fmt.Sprintf(\"agent-injector.%s.svc\", mgrNamespace)\n\tdnsNames := []string{\"agent-injector\", \"agent-injector.\" + mgrNamespace, commonName}\n\n\t// server cert config\n\tcert := &x509.Certificate{\n\t\tDNSNames:     dnsNames,\n\t\tSerialNumber: big.NewInt(0xefecab1),\n\t\tSubject: pkix.Name{\n\t\t\tCommonName:   commonName,\n\t\t\tOrganization: []string{\"telepresence.io\"},\n\t\t},\n\t\tNotBefore:    time.Now(),\n\t\tNotAfter:     time.Now().AddDate(10, 0, 0), // Valid 10 years\n\t\tSubjectKeyId: bigIntHash(caPrivKey.N),\n\t\tExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},\n\t\tKeyUsage:     x509.KeyUsageDigitalSignature,\n\t}\n\n\tserverPrivateKey, err := rsa.GenerateKey(cryptorand.Reader, 4096)\n\tif err != nil {\n\t\treturn nil, nil, nil, fmt.Errorf(\"failed to server private key: %w\", err)\n\t}\n\n\tserverCert, err := x509.CreateCertificate(cryptorand.Reader, cert, caCert, &serverPrivateKey.PublicKey, caPrivKey)\n\tif err != nil {\n\t\treturn nil, nil, nil, fmt.Errorf(\"failed to sign the server certificate: %w\", err)\n\t}\n\n\tif keyPem, err = ToPEM(\"key.pem\", \"RSA PRIVATE KEY\", x509.MarshalPKCS1PrivateKey(serverPrivateKey)); err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\tif crtPem, err = ToPEM(\"crt.pem\", \"CERTIFICATE\", serverCert); err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\treturn crtPem, keyPem, caPem, nil\n}\n\nfunc bigIntHash(n *big.Int) []byte {\n\th := sha1.New()\n\t_, _ = h.Write(n.Bytes())\n\treturn h.Sum(nil)\n}\n\n// ToPEM returns the PEM encoding of data.\nfunc ToPEM(file, keyType string, data []byte) ([]byte, error) {\n\twrt := bytes.Buffer{}\n\tif err := pem.Encode(&wrt, &pem.Block{Type: keyType, Bytes: data}); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to PEM encode %s %s: %w\", keyType, file, err)\n\t}\n\treturn wrt.Bytes(), nil\n}\n"
  },
  {
    "path": "build-aux/docker/images/Dockerfile.client",
    "content": "# syntax = docker/dockerfile:1.3\n\n# Copyright 2020-2022 Datawire. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM --platform=$BUILDPLATFORM golang:alpine as telepresence-build\n\nRUN apk add --no-cache gcc musl-dev fuse-dev libcap binutils-gold\n\nWORKDIR telepresence\nCOPY go.mod .\nCOPY go.sum .\nCOPY cmd/cobraparser/ cmd/cobraparser/\nCOPY cmd/telepresence/ cmd/telepresence/\nCOPY pkg/ pkg/\nCOPY rpc/ rpc/\nCOPY charts/ charts/\nCOPY build-output/version.txt .\nCOPY build-output/helm-version.txt .\n\nARG TARGETOS\nARG TARGETARCH\n\nRUN \\\n    --mount=type=cache,target=/root/.cache/go-build \\\n    --mount=type=cache,target=/go/pkg/mod \\\n    GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /usr/local/bin/ -trimpath -tags docker -ldflags=\"-s -w -X=$(go list ./pkg/version).Version=$(cat version.txt) -X=$(go list ./pkg/version).HelmVersion=$(cat helm-version.txt)\" ./cmd/telepresence/...\n\n# setcap is necessary because the process will listen to privileged ports\nRUN setcap 'cap_net_bind_service+ep' /usr/local/bin/telepresence\n\n# The telepresence target is the one that gets published. It aims to be a small as possible.\nFROM alpine as telepresence\n\nRUN apk add --no-cache ca-certificates iptables\n\n# the telepresence binary\nCOPY --from=telepresence-build /usr/local/bin/telepresence /usr/local/bin\n\nENTRYPOINT [\"telepresence\"]\nCMD []\n"
  },
  {
    "path": "build-aux/docker/images/Dockerfile.routecontroller",
    "content": "# syntax = docker/dockerfile:1.3\n\n# Copyright 2024 Datawire. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM --platform=$BUILDPLATFORM golang:alpine AS routecontroller-build\n\nWORKDIR telepresence\nCOPY go.mod go.sum .\nCOPY cmd/routecontroller/ cmd/routecontroller/\nCOPY pkg/ pkg/\nCOPY build-output/version.txt .\n\nARG TARGETOS\nARG TARGETARCH\n\nRUN \\\n    --mount=type=cache,target=/root/.cache/go-build \\\n    --mount=type=cache,target=/go/pkg/mod \\\n    GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /usr/local/bin/route-controller -trimpath -ldflags=\"-s -w\" ./cmd/routecontroller/\n\nFROM alpine AS routecontroller\n\nRUN apk add --no-cache ca-certificates iptables ip6tables\n\nCOPY --from=routecontroller-build /usr/local/bin/route-controller /usr/local/bin/route-controller\n\nENTRYPOINT [\"/usr/local/bin/route-controller\"]\nCMD []\n"
  },
  {
    "path": "build-aux/docker/images/Dockerfile.traffic",
    "content": "# syntax = docker/dockerfile:1.3\n\n# Copyright 2020-2022 Datawire. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nFROM --platform=$BUILDPLATFORM golang:alpine as tel2-build\n\nRUN apk add --no-cache gcc musl-dev fuse-dev libcap binutils-gold\n\nWORKDIR telepresence\nCOPY go.mod go.sum .\nCOPY cmd/ cmd/\nCOPY pkg/ pkg/\nCOPY rpc/ rpc/\nCOPY charts/ charts/\nCOPY build-output/version.txt .\n\nARG TARGETOS\nARG TARGETARCH\n\nRUN \\\n    --mount=type=cache,target=/root/.cache/go-build \\\n    --mount=type=cache,target=/go/pkg/mod \\\n    GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /usr/local/bin/ -trimpath -ldflags=\"-s -w -X=$(go list ./pkg/version).Version=$(cat version.txt)\" ./cmd/traffic/...\n\n# The tel2 target is the one that gets published. It aims to be a small as possible.\nFROM alpine as tel2\n\nRUN apk add --no-cache ca-certificates iptables\n\n# the traffic binary\nCOPY --from=tel2-build /usr/local/bin/traffic /usr/local/bin\n\nRUN \\\n  mkdir /tel_app_mounts && \\\n  chgrp -R 0 /tel_app_mounts && \\\n  chmod -R g=u /tel_app_mounts && \\\n  mkdir -p /home/telepresence && \\\n  chgrp -R 0 /home/telepresence && \\\n  chmod -R g=u /home/telepresence && \\\n  chmod 0777 /home/telepresence\n\nENTRYPOINT [\"traffic\"]\nCMD []\n"
  },
  {
    "path": "build-aux/docker/images/Dockerfile.winbuild",
    "content": "FROM golang:1.19.4\n\n\nRUN apt-get update && \\\n    apt-get install -y \\\n            apt-transport-https \\\n            ca-certificates \\\n            curl \\\n            gnupg \\\n            lsb-release && \\\n    curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \\\n    echo \\\n        \"deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian \\\n        $(lsb_release -cs) stable\" | tee /etc/apt/sources.list.d/docker.list > /dev/null\n\nRUN apt-get update && \\\n    apt-get install -y docker-ce docker-ce-cli containerd.io"
  },
  {
    "path": "build-aux/genversion/main.go",
    "content": "// Copyright 2021 Datawire. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//     http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\npackage main\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\n\t//nolint:depguard // This short script has no logging and no Contexts.\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\tignore \"github.com/sabhiram/go-gitignore\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// isReleased returns true if a release tag exist for the given version\n// A release tag is a tag that represents a semver version, without pre-version\n// or build suffixes, that is prefixed with \"v\", e.g. \"v1.2.3\" is considered\n// a release tag whereas \"v1.2.3-rc.3\" isn't.\nfunc isReleased(v semver.Version) bool {\n\tv.Build = nil\n\tv.Pre = nil\n\treturn exec.Command(\"git\", \"describe\", \"v\"+v.String()).Run() == nil\n}\n\n// dirMD5 computes the MD5 checksum of all files found when recursively\n// traversing a directory, skipping .gitignore's, _test/, and _test.go. The\n// general idea is to avoid rebuilds and pushes when repeatedly running tests,\n// even if the tests themselves actually change.\nfunc dirMD5(root string) ([]byte, error) {\n\tign, err := ignore.CompileIgnoreFile(filepath.Join(root, \".gitignore\"))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\td := md5.New()\n\ttestMach := fmt.Sprintf(\"_test%c\", filepath.Separator)\n\tisTest := func(path string) bool {\n\t\treturn strings.Contains(path, testMach) || strings.HasSuffix(path, \"_test.go\")\n\t}\n\terr = filepath.Walk(root, func(path string, info fs.FileInfo, err error) error {\n\t\tif err == nil && info.Mode().IsRegular() && !(ign.MatchesPath(path) || isTest(path)) {\n\t\t\tvar data []byte\n\t\t\tif data, err = os.ReadFile(path); err == nil {\n\t\t\t\td.Write(data)\n\t\t\t}\n\t\t}\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn d.Sum(make([]byte, 0, md5.Size)), nil\n}\n\nfunc getLatestChangelogVersion() (v semver.Version, released bool, err error) {\n\tdata, err := os.ReadFile(\"CHANGELOG.yml\")\n\tif err != nil {\n\t\treturn v, false, err\n\t}\n\tnotes := struct {\n\t\tItems []map[string]any `json:\"items\"`\n\t}{}\n\tif err = yaml.Unmarshal(data, &notes); err != nil {\n\t\treturn v, false, err\n\t}\n\tif len(notes.Items) == 0 {\n\t\treturn v, false, fmt.Errorf(\"no versions in CHANGELOG.yml\")\n\t}\n\ttop := notes.Items[0]\n\tvs, ok := top[\"version\"].(string)\n\tif !ok {\n\t\treturn v, false, fmt.Errorf(\"no version in top entry ofCHANGELOG.yml\")\n\t}\n\tv, err = semver.Parse(vs)\n\tif err != nil {\n\t\treturn v, false, err\n\t}\n\tif date, ok := top[\"date\"].(string); ok {\n\t\tif _, dateErr := time.Parse(\"2006-01-02\", date); dateErr == nil {\n\t\t\treleased = true\n\t\t}\n\t}\n\treturn v, released, nil\n}\n\nfunc Main() error {\n\tcmd := exec.Command(\"git\", \"describe\", \"--tags\", \"--match=v*\")\n\tcmd.Stderr = os.Stderr\n\tgitDescBytes, err := cmd.Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to git describe: %w\", err)\n\t}\n\tgitDescStr := strings.TrimSuffix(strings.TrimPrefix(string(gitDescBytes), \"v\"), \"\\n\")\n\tgitDescVer, err := semver.Parse(gitDescStr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to parse semver %s: %w\", gitDescStr, err)\n\t}\n\n\tclv, released, err := getLatestChangelogVersion()\n\tif err == nil {\n\t\tgitDescVer.Major = clv.Major\n\t\tgitDescVer.Minor = clv.Minor\n\t\tgitDescVer.Patch = clv.Patch\n\t} else {\n\t\treleased = isReleased(gitDescVer)\n\t}\n\n\t// Bump to next patch version only if the version has been released.\n\tif released {\n\t\tgitDescVer.Patch++\n\t}\n\n\tout := os.Stdout\n\n\t// If an additional arg has been used, we include it in the tag\n\tif len(os.Args) >= 2 {\n\t\t// gitDescVer.Pre[0] contains the number of commits since the last tag and the\n\t\t// shortHash with a 'g' appended.  Since the first section isn't relevant,\n\t\t// we get the shortHash this way since we don't need that extra information.\n\t\tcmd = exec.Command(\"git\", \"rev-parse\", \"--short\", \"HEAD\")\n\t\tcmd.Stderr = os.Stderr\n\t\tshortHash, err := cmd.Output()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to git rev-parse: %w\", err)\n\t\t}\n\t\tif _, err = fmt.Fprintf(out, \"v%d.%d.%d-%s-%s\\n\", gitDescVer.Major, gitDescVer.Minor, gitDescVer.Patch, os.Args[1], shortHash); err != nil {\n\t\t\treturn fmt.Errorf(\"unable to printf: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Append a mangled md5 if the directory is dirty.\n\tcmd = exec.Command(\"git\", \"status\", \"--short\")\n\tcmd.Stderr = os.Stderr\n\tstatusBytes, err := cmd.Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to git rev-parse: %w\", err)\n\t}\n\tif len(statusBytes) > 0 {\n\t\tvar md5Out []byte\n\t\tmd5Out, err = dirMD5(\".\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to compute MD5: %w\", err)\n\t\t}\n\n\t\tb64 := base64.RawURLEncoding.EncodeToString(md5Out)\n\t\tb64 = strings.ReplaceAll(b64, \"_\", \"Z\")\n\t\tb64 = strings.ReplaceAll(b64, \"-\", \"z\")\n\t\t_, err = fmt.Fprintf(out, \"v%s-%s\\n\", gitDescVer, b64)\n\t} else {\n\t\t_, err = fmt.Fprintf(out, \"v%s\\n\", gitDescVer)\n\t}\n\treturn err\n}\n\nfunc main() {\n\tif err := Main(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"%s: error: %v\\n\", filepath.Base(os.Args[0]), err)\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "build-aux/image-importer.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  creationTimestamp: null\n  labels:\n    app: image-importer\n  name: image-importer\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: image-importer\n  strategy: {}\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: image-importer\n    spec:\n      containers:\n      - image: ubuntu\n        name: ubuntu\n        resources: {}\n        command:\n          - sleep\n          - \"10000000\"\n        volumeMounts:\n          - mountPath: /run/k3s\n            name: run\n          - mountPath: /var/lib/rancher\n            name: rancher\n          - mountPath: /hostbin\n            name: bin\n      volumes:\n      - name: run\n        hostPath:\n          path: /run/k3s\n      - name: rancher\n        hostPath:\n          path: /var/lib/rancher\n      - name: bin\n        hostPath:\n          path: /bin\nstatus: {}\n"
  },
  {
    "path": "build-aux/main.mk",
    "content": "# Copyright 2020-2021 Datawire.  All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 file deals with the \"main\" flow of Make.  The user-facing\n# targets, the generate/build/release cycle.  Try to keep boilerplate\n# out of this file.  Try to keep this file simple; anything complex or\n# clever should probably be factored into a separate file.\n\n# All build artifacts that are files end up in $(BUILDDIR).\nBUILDDIR=build-output\n\nBINDIR=$(BUILDDIR)/bin\n\nRELEASEDIR=$(BUILDDIR)/release\n\nbindir ?= $(or $(shell go env GOBIN),$(shell go env GOPATH|cut -d: -f1)/bin)\n\n# DOCKER_BUILDKIT is _required_ by our Dockerfile, since we use\n# Dockerfile extensions for the Go build cache.  See\n# https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md.\nexport DOCKER_BUILDKIT := 1\n\n.PHONY: FORCE\nFORCE:\n\nEXTERNAL_FUSEFTP ?= 0\nLINKED_FUSEFTP ?= 1\n\n# Build with CGO_ENABLED=0 on all platforms to ensure that the binary is as\n# portable as possible, but we must make an exception for darwin, because\n# the Go implementation of the DNS resolver doesn't work properly there unless\n# it's using clib\nifeq ($(GOOS),darwin)\nCGO_ENABLED ?= 1\nelse\nifeq ($(GOOS),linux)\n# The winfsp module requires CGO on Linux.\nCGO_ENABLED ?= $(LINKED_FUSEFTP)\nelse\nCGO_ENABLED ?= 0\nendif\nendif\n\nifeq ($(GOOS),windows)\nBEXE=.exe\nBZIP=.zip\nelse\nBEXE=\nBZIP=\nendif\n\n# Generate: artifacts that get checked in to Git\n# ==============================================\n\n$(BUILDDIR)/go1%.src.tar.gz:\n\tmkdir -p $(BUILDDIR)\n\tcurl -o $@ --fail -L https://dl.google.com/go/$(@F)\n\n.PHONY: clean\nclean:\n\trm -rf $(BUILDDIR)\n\n.PHONY: protoc-clean\nprotoc-clean:\n\tfind ./rpc -name '*.go' -delete\n\n.PHONY: protoc\nprotoc: protoc-clean $(tools/protoc) $(tools/protoc-gen-go) $(tools/protoc-gen-go-grpc)\n\t$(tools/protoc) \\\n\t  -I rpc \\\n\t  \\\n\t  --go_out=./rpc \\\n\t  --go_opt=module=github.com/telepresenceio/telepresence/rpc/v2 \\\n\t  \\\n\t  --go-grpc_out=./rpc \\\n\t  --go-grpc_opt=module=github.com/telepresenceio/telepresence/rpc/v2 \\\n\t  \\\n\t  --proto_path=. \\\n\t  $$(find ./rpc/ -name '*.proto')\n\n.PHONY: generate\ngenerate: ## (Generate) Update generated files that get checked in to Git\ngenerate: generate-clean\ngenerate: protoc $(tools/go-mkopensource) $(BUILDDIR)/$(shell go env GOVERSION | grep -oE '^go[0-9]+\\.[0-9]+\\.[0-9]+').src.tar.gz\n\tcd ./rpc && export GOFLAGS=-mod=mod && go mod tidy && go mod vendor && rm -rf vendor\n\tcd ./pkg/vif/testdata/router && export GOFLAGS=-mod=mod && go mod tidy && go mod vendor && rm -rf vendor\n\tcd ./tools/src/test-report && export GOFLAGS=-mod=mod && go mod tidy && go mod vendor && rm -rf vendor\n\tcd ./cmd/teleroute && $(MAKE) rpc/teleroute/.rsync-stamp\n\tcd ./cmd/teleroute && go mod tidy\n\tcd ./cmd/teleroute/rpc && go mod tidy\n\tcd ./integration_test/testdata/echo-server && export GOFLAGS=-mod=mod && go mod tidy && go mod vendor && rm -rf vendor\n\n\texport GOFLAGS=-mod=mod && go mod tidy && go mod vendor\n\n\tmkdir -p $(BUILDDIR)\n\t$(tools/go-mkopensource) --gotar=$(filter %.src.tar.gz,$^) --ignore-dirty --output-format=txt --package=mod --application-type=external \\\n\t\t--unparsable-packages build-aux/unparsable-packages.yaml >$(BUILDDIR)/DEPENDENCIES.txt\n\tsed 's/\\(^.*the Go language standard library .\"std\".[ ]*v[1-9]\\.[0-9]*\\)\\..../\\1    /' $(BUILDDIR)/DEPENDENCIES.txt >DEPENDENCIES.md\n\n\tprintf \"Telepresence CLI incorporates Free and Open Source software under the following licenses:\\n\\n\" > DEPENDENCY_LICENSES.md\n\t$(tools/go-mkopensource) --gotar=$(filter %.src.tar.gz,$^) --ignore-dirty --output-format=txt --package=mod \\\n\t\t--output-type=json --application-type=external --unparsable-packages build-aux/unparsable-packages.yaml > $(BUILDDIR)/DEPENDENCIES.json\n\tjq -r '.licenseInfo | to_entries | .[] | \"* [\" + .key + \"](\" + .value + \")\"' $(BUILDDIR)/DEPENDENCIES.json > $(BUILDDIR)/LICENSES.txt\n\tsed -e 's/\\[\\([^]]*\\)]()/\\1/' $(BUILDDIR)/LICENSES.txt >> DEPENDENCY_LICENSES.md\n\trsync -vc DEPENDENCY_LICENSES.md docs/licenses.md\n\n\trm -rf vendor\n\n# Build: artifacts that don't get checked in to Git\n# =================================================\n\nTELEPRESENCE=$(BINDIR)/telepresence$(BEXE)\n\ngenerate: docs-files\n\n.PHONY: generate-clean\ngenerate-clean: ## (Generate) Delete generated files\n\trm -rf ./rpc/vendor\n\trm -rf ./vendor\n\trm -f DEPENDENCIES.md\n\trm -f DEPENDENCY_LICENSES.md\n\trm -f docs/release-notes.md*\n\trm -f docs/README.md\n\trm -f docs/helm/values.schema.json\n\nCHANGELOG.yml: FORCE\n\t@# Check if the version is in the x.x.x format (GA release)\n\tif echo \"$(TELEPRESENCE_VERSION)\" | grep -qE 'v[0-9]+\\.[0-9]+\\.[0-9]+$$'; then \\\n\t\techo $$file; \\\n\t\tsed -i.bak -r \"s/date: (TBD|\\(TBD\\)|\\\"TBD\\\"|\\\"\\(TBD\\)\\\")$$/date: $$(date +'%Y-%m-%d')/\" CHANGELOG.yml; \\\n\t\trm -f CHANGELOG.yml.bak; \\\n\t\tgit add CHANGELOG.yml; \\\n\tfi\n\ndocs-files: docs/README.md docs/release-notes.md docs/release-notes.mdx docs/variables.yml docs/helm/values.schema.json docs/reference/cli/telepresence.md\n\ndocs/reference/cli/telepresence.md: $(TELEPRESENCE)\n\t$(TELEPRESENCE) man-pages --dir $(@D)\n\tgit add $(@D)\n\ndocs/README.md: docs/doc-links.yml $(tools/tocgen)\n\t$(tools/tocgen) --input $< > $@\n\tgit add $@\n\ndocs/release-notes.md: CHANGELOG.yml $(tools/relnotesgen)\n\t$(tools/relnotesgen) --input $< > $@\n\tgit add $@\n\ndocs/release-notes.mdx: CHANGELOG.yml $(tools/relnotesgen)\n\t$(tools/relnotesgen) --mdx --input $< > $@\n\tgit add $@\n\ndocs/variables.yml: CHANGELOG.yml $(tools/relnotesgen)\n\t$(tools/relnotesgen) --variables --input $< > $@\n\tgit add $@\n\ndocs/helm/values.schema.json: charts/telepresence-oss/values.schema.yaml $(tools/y2j)\n\tmkdir -p $(@D)\n\t$(tools/y2j) < $< > $@\n\tgit add $@\n\nPKG_VERSION = $(shell go list ./pkg/version)\n\nifeq ($(GOOS),windows)\nTELEPRESENCE_INSTALLER=$(BINDIR)/telepresence$(BZIP)\nendif\n\n.PHONY: build\nbuild: $(TELEPRESENCE) ## (Build) Produce a `telepresence` binary for GOOS/GOARCH\n\n# We might be building for arm64 on a mac that doesn't have an M1 chip\n# (which is definitely the case with circle), so GOARCH may be set for that,\n# but we need to ensure it's using the host's architecture so the go command runs successfully.\nifeq ($(GOHOSTOS),darwin)\n\tsdkroot=SDKROOT=$(shell xcrun --sdk macosx --show-sdk-path)\nelse\n\tsdkroot=\nendif\n\nBUILD_TAGS=\nbuild-deps:\n\nifeq ($(DOCKER_BUILD),1)\nBUILD_TAGS=-tags docker\nelse\nifeq ($(EXTERNAL_FUSEFTP),1)\nBUILD_TAGS=-tags external_fuseftp\npkg/client/remotefs/fuseftp.bits:\n\ttouch $@\nelse\nifeq ($(LINKED_FUSEFTP),1)\nBUILD_TAGS=-tags linked_fuseftp\npkg/client/remotefs/fuseftp.bits:\n\ttouch $@\nelse\nFUSEFTP_VERSION=$(shell go list -m -f {{.Version}} github.com/telepresenceio/go-fuseftp/rpc)\n\n$(BUILDDIR)/fuseftp-$(GOOS)-$(GOARCH)$(BEXE): go.mod\n\tmkdir -p $(BUILDDIR)\n\tcurl --fail -L https://github.com/telepresenceio/go-fuseftp/releases/download/$(FUSEFTP_VERSION)/fuseftp-$(GOOS)-$(GOARCH)$(BEXE) -o $@\n\npkg/client/remotefs/fuseftp.bits: $(BUILDDIR)/fuseftp-$(GOOS)-$(GOARCH)$(BEXE) FORCE\n\tcp $< $@\nendif\nendif\nendif\nbuild-deps: pkg/client/remotefs/fuseftp.bits\n\npkg/client/cli/docker/compose/dc-cli.json: go.mod go.mod cmd/cobraparser/main.go\n\tgo mod tidy\n\t(cd cmd/cobraparser && go mod tidy) && GOOS= GOARCH= go run cmd/cobraparser/main.go docker compose > $@\n\nbuild-deps: pkg/client/cli/docker/compose/dc-cli.json\n\nifeq ($(GOHOSTOS),windows)\nWINTUN_VERSION=0.14.1\n$(BUILDDIR)/wintun-$(WINTUN_VERSION)/wintun/bin/$(GOARCH)/wintun.dll:\n\tmkdir -p $(BUILDDIR)\n\tcurl --fail -L https://www.wintun.net/builds/wintun-$(WINTUN_VERSION).zip -o $(BUILDDIR)/wintun-$(WINTUN_VERSION).zip\n\trm -rf  $(BUILDDIR)/wintun-$(WINTUN_VERSION)\n\tunzip $(BUILDDIR)/wintun-$(WINTUN_VERSION).zip -d $(BUILDDIR)/wintun-$(WINTUN_VERSION)\n$(BINDIR)/wintun.dll: $(BUILDDIR)/wintun-$(WINTUN_VERSION)/wintun/bin/$(GOARCH)/wintun.dll\n\tmkdir -p $(@D)\n\tcp $< $@\n\nwintun.dll: $(BINDIR)/wintun.dll\n\nwinfsp.msi:\n\tmkdir -p $(BUILDDIR)\n\tcurl --fail -L https://github.com/winfsp/winfsp/releases/download/v1.11/winfsp-1.11.22176.msi -o $(BUILDDIR)/winfsp.msi\n\nsshfs-win.msi:\n\tmkdir -p $(BUILDDIR)\n\tcurl --fail -L https://github.com/billziss-gh/sshfs-win/releases/download/v3.7.21011/sshfs-win-3.7.21011-x64.msi -o $(BUILDDIR)/sshfs-win.msi\nendif\n\nHELM_VERSION = $(shell go mod edit -json | jq -r '.Require[] | select(.Path == \"helm.sh/helm/v3\") | .Version')\nLDFLAGS := -X=$(PKG_VERSION).Version=$(TELEPRESENCE_VERSION) -X=$(PKG_VERSION).HelmVersion=$(HELM_VERSION)\nifneq ($(DEBUG),1)\n  # strip debug information and dwarf\n  LDFLAGS := -s -w $(LDFLAGS)\nendif\n$(info LDFLAGS=$(LDFLAGS))\n\nifeq ($(GOOS),linux)\nifeq ($(GOARCH),arm64)\nifeq ($(CGO_ENABLED),1)\n\tBUILD_ENV := CC=aarch64-linux-gnu-gcc\nendif\nendif\nendif\n$(info BUILD_ENV=$(BUILD_ENV))\n\n$(TELEPRESENCE): build-deps FORCE\nifeq ($(GOHOSTOS),windows)\n$(TELEPRESENCE): build-deps $(BINDIR)/wintun.dll FORCE\nendif\n\tmkdir -p $(@D)\nifeq ($(DOCKER_BUILD),1)\n\tCGO_ENABLED=$(CGO_ENABLED) $(sdkroot) go build $(BUILD_TAGS) -trimpath -ldflags=\"$(LDFLAGS)\" -o $@ ./cmd/telepresence\nelse\n# -buildmode=pie enables PIE compilation for binary harderning. Default on darwin and windows (since 1.23) but not in linux.\n\t$(BUILD_ENV) CGO_ENABLED=$(CGO_ENABLED) $(sdkroot) go build $(BUILD_TAGS) -buildmode=pie -trimpath -ldflags=\"$(LDFLAGS)\" -o $@ ./cmd/telepresence\nendif\n\nifeq ($(GOOS),windows)\n$(TELEPRESENCE_INSTALLER): $(TELEPRESENCE)\n\tbash ./packaging/windows-package.sh\nendif\n\n.PHONY: release-binary\nifeq ($(GOOS),windows)\nrelease-binary: $(TELEPRESENCE_INSTALLER)\n\tmkdir -p $(RELEASEDIR)\n\tcp $(TELEPRESENCE_INSTALLER) $(RELEASEDIR)/telepresence-windows-$(GOARCH)$(BZIP)\nelse\nrelease-binary: $(TELEPRESENCE)\n\tmkdir -p $(RELEASEDIR)\n\tcp $(TELEPRESENCE) $(RELEASEDIR)/telepresence-$(GOOS)-$(GOARCH)$(BEXE)\nendif\n\n.PHONY: setup-build-dir\nsetup-build-dir:\n\tmkdir -p $(BUILDDIR)\n\tprintf $(TELEPRESENCE_VERSION) > $(BUILDDIR)/version.txt ## Pass version in a file instead of a --build-arg to maximize cache usage\n\tprintf $(HELM_VERSION) > $(BUILDDIR)/helm-version.txt\n\nTELEPRESENCE_SEMVER=$(patsubst v%,%,$(TELEPRESENCE_VERSION))\nCLIENT_IMAGE_FQN=$(TELEPRESENCE_REGISTRY)/telepresence:$(TELEPRESENCE_SEMVER)\nTEL2_IMAGE_FQN=$(TELEPRESENCE_REGISTRY)/tel2:$(TELEPRESENCE_SEMVER)\n\n.PHONY: images-deps\nimages-deps: build-deps setup-build-dir\n\n.PHONY: tel2-image\ntel2-image: images-deps\n\t$(eval PLATFORM_ARG := $(if $(TELEPRESENCE_TEL2_IMAGE_PLATFORM), --platform=$(TELEPRESENCE_TEL2_IMAGE_PLATFORM),))\n\tdocker build $(PLATFORM_ARG) --target tel2 --tag tel2 --tag $(TEL2_IMAGE_FQN) -f build-aux/docker/images/Dockerfile.traffic .\n\n.PHONY: client-image\nclient-image: images-deps\n\tdocker build --target telepresence --tag telepresence --tag $(CLIENT_IMAGE_FQN) -f build-aux/docker/images/Dockerfile.client .\n\n.PHONY: push-tel2-image\npush-tel2-image: tel2-image ## (Build) Push the manager/agent container image to $(TELEPRESENCE_REGISTRY)\n\tdocker push $(TEL2_IMAGE_FQN)\n\n.PHONY: save-tel2-image\nsave-tel2-image: tel2-image\n\tdocker save $(TEL2_IMAGE_FQN) > $(BUILDDIR)/tel2-image.tar\n\n.PHONY: push-client-image\npush-client-image: client-image ## (Build) Push the client container image to $(TELEPRESENCE_REGISTRY)\n\tdocker push $(CLIENT_IMAGE_FQN)\n\nROUTECONTROLLER_IMAGE_FQN=$(TELEPRESENCE_REGISTRY)/route-controller:$(TELEPRESENCE_SEMVER)\n\n.PHONY: routecontroller-image\nroutecontroller-image: images-deps  ## (Build) Build the route-controller DaemonSet image\n\t$(eval PLATFORM_ARG := $(if $(TELEPRESENCE_ROUTECONTROLLER_IMAGE_PLATFORM), --platform=$(TELEPRESENCE_ROUTECONTROLLER_IMAGE_PLATFORM),))\n\tdocker build $(PLATFORM_ARG) --target routecontroller --tag route-controller --tag $(ROUTECONTROLLER_IMAGE_FQN) \\\n\t    -f build-aux/docker/images/Dockerfile.routecontroller .\n\n.PHONY: push-routecontroller-image\npush-routecontroller-image: routecontroller-image ## (Build) Push the route-controller DaemonSet image to $(TELEPRESENCE_REGISTRY)\n\tdocker push $(ROUTECONTROLLER_IMAGE_FQN)\n\n.PHONY: push-images\npush-images: push-tel2-image push-client-image push-routecontroller-image\n\n.PHONY: helm-chart\nhelm-chart: $(BUILDDIR)/telepresence-oss-chart.tgz\n\n$(BUILDDIR)/telepresence-oss-chart.tgz: $(wildcard charts/**/*)\n\tmkdir -p $(BUILDDIR)\n\tgo run packaging/helmpackage.go -o $@ -v $(TELEPRESENCE_SEMVER)\n\n.PHONY: clobber\nclobber:  clobber-tools generate-clean ## (Build) Remove all build artifacts and tools\n\trm -rf $(BUILDDIR)\n\trm -rf cmd/teleroute/rpc\n\trm -rf cmd/teleroute/build-output\n\trm -f pkg/client/cli/docker/compose/dc-cli.json\n\trm -f docs/helm/values.schema.json\n\n# Release: Push the artifacts places, update pointers ot them\n# ===========================================================\n\n.PHONY: prepare-release\nprepare-release: generate\n\tgo mod edit -require=github.com/telepresenceio/telepresence/rpc/v2@$(TELEPRESENCE_VERSION)\n\tgit add go.mod\n\n\t(cd pkg/vif/testdata/router && \\\n\t  go mod edit -require=github.com/telepresenceio/telepresence/rpc/v2@$(TELEPRESENCE_VERSION) && \\\n\t  git add go.mod)\n\n\tgit commit --signoff --message='Prepare $(TELEPRESENCE_VERSION)' || true\n\n\tgit tag --annotate --message='$(TELEPRESENCE_VERSION)' $(TELEPRESENCE_VERSION)\n\tgit tag --annotate --message='$(TELEPRESENCE_VERSION)' rpc/$(TELEPRESENCE_VERSION)\n\n.PHONY: push-tags\npush-tags:\n\tgit push origin $(TELEPRESENCE_VERSION)\n\tgit push origin rpc/$(TELEPRESENCE_VERSION)\n\n# Prerequisites:\n# The awscli command must be installed and configured with credentials to upload\n# to the datawire-static-files bucket.\n.PHONY: push-executable\npush-executable: build ## (Release) Upload the executable to S3\nifeq ($(GOHOSTOS), windows)\n\tpackaging/windows-package.sh\n\tAWS_PAGER=\"\" aws s3api put-object \\\n\t\t--bucket datawire-static-files \\\n\t\t--key tel2-oss/$(GOHOSTOS)/$(GOARCH)/$(TELEPRESENCE_SEMVER)/telepresence.zip \\\n\t\t--body $(BINDIR)/telepresence.zip\n\tAWS_PAGER=\"\" aws s3api put-object \\\n\t\t--region us-east-1 \\\n\t\t--bucket datawire-static-files \\\n\t\t--key tel2-oss/$(GOHOSTOS)/$(GOARCH)/$(TELEPRESENCE_SEMVER)/telepresence-setup.exe \\\n\t\t--body $(BINDIR)/telepresence-setup.exe\nelse\n\tAWS_PAGER=\"\" aws s3api put-object \\\n\t\t--bucket datawire-static-files \\\n\t\t--key tel2-oss/$(GOHOSTOS)/$(GOARCH)/$(TELEPRESENCE_SEMVER)/telepresence \\\n\t\t--body $(BINDIR)/telepresence\nendif\n\n# Prerequisites:\n# The awscli command must be installed and configured with credentials to upload\n# to the datawire-static-files bucket.\n.PHONY: promote-to-stable\npromote-to-stable: ## (Release) Update stable.txt in S3\n\tmkdir -p $(BUILDDIR)\n\techo $(TELEPRESENCE_SEMVER) > $(BUILDDIR)/stable.txt\n\tAWS_PAGER=\"\" aws s3api put-object \\\n\t\t--bucket datawire-static-files \\\n\t\t--key tel2-oss/$(GOHOSTOS)/$(GOARCH)/stable.txt \\\n\t\t--body $(BUILDDIR)/stable.txt\nifeq ($(GOHOSTOS), darwin)\n# Since the enterprise version is built from a different makefile, we only use the oss target here. Ref: https://github.com/telepresenceio/telepresence/pull/3626#issuecomment-2200150895\n\tpackaging/homebrew-package.sh $(TELEPRESENCE_SEMVER)\nendif\n\n# Prerequisites:\n# The awscli command must be installed and configured with credentials to upload\n# to the datawire-static-files bucket.\n.PHONY: promote-nightly\npromote-nightly: ## (Release) Update nightly.txt in S3\n\tmkdir -p $(BUILDDIR)\n\techo $(TELEPRESENCE_SEMVER) > $(BUILDDIR)/nightly.txt\n\tAWS_PAGER=\"\" aws s3api put-object \\\n\t\t--bucket datawire-static-files \\\n\t\t--key tel2-oss/$(GOHOSTOS)/$(GOARCH)/nightly.txt \\\n\t\t--body $(BUILDDIR)/nightly.txt\n\n# Quality Assurance: Make sure things are good\n# ============================================\n\n.PHONY: lint-deps\nlint-deps: build-deps ## (QA) Everything necessary to lint\nlint-deps: $(tools/protolint)\nifneq ($(GOHOSTOS), windows)\nlint-deps: $(tools/shellcheck)\nendif\n\n.PHONY: build-tests\nbuild-tests: build-deps ## (Test) Build (but don't run) the test suite.  Useful for pre-loading the Go build cache.\n\tgo list ./... | xargs -n1 go test -c -o /dev/null\n\nshellscripts += ./packaging/homebrew-package.sh\nshellscripts += ./packaging/windows-package.sh\n.PHONY: lint lint-rpc lint-go\n\nlint: lint-rpc lint-go\n\nlint-go: lint-deps ## (QA) Run the golangci-lint\nifeq ($(GOOS),windows)\n\t@ver=$$(curl -fsSL 'https://api.github.com/repos/golangci/golangci-lint/releases/latest' | grep -o '\"tag_name\": *\"[^\"]*\"' | head -1 | cut -d'\"' -f4) && \\\n\tdocker run -e GOOS=$(GOOS) --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$$ver:/root/.cache -w /app golangci/golangci-lint:$$ver golangci-lint \\\n\trun --timeout 8m ./cmd/cobraparser/... ./cmd/telepresence/... ./integration_test/... ./pkg/...\nelse\n\t@ver=$$(curl -fsSL 'https://api.github.com/repos/golangci/golangci-lint/releases/latest' | grep -o '\"tag_name\": *\"[^\"]*\"' | head -1 | cut -d'\"' -f4) && \\\n\tdocker run -e GOOS=$(GOOS) --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$$ver:/root/.cache -w /app golangci/golangci-lint:$$ver golangci-lint \\\n\trun --timeout 8m ./...\nendif\n\nlint-rpc: lint-deps ## (QA) Run rpc linter\n\t$(tools/protolint) lint rpc\nifneq ($(GOHOSTOS), windows)\n\t$(tools/shellcheck) $(shellscripts)\nendif\n\n.PHONY: format\nformat: lint-deps ## (QA) Automatically fix linter complaints\nifeq ($(GOHOSTOS),windows)\n\t@ver=$$(curl -fsSL 'https://api.github.com/repos/golangci/golangci-lint/releases/latest' | grep -o '\"tag_name\": *\"[^\"]*\"' | head -1 | cut -d'\"' -f4) && \\\n\tdocker run -e GOOS=$(GOOS) --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$$ver:/root/.cache -w /app golangci/golangci-lint:$$ver golangci-lint \\\n\trun --timeout 8m --fix ./cmd/telepresence/... ./integration_test/... ./pkg/...\nelse\n\t@ver=$$(curl -fsSL 'https://api.github.com/repos/golangci/golangci-lint/releases/latest' | grep -o '\"tag_name\": *\"[^\"]*\"' | head -1 | cut -d'\"' -f4) && \\\n\tdocker run -e GOOS=$(GOOS) --rm -v $$(pwd):/app -v ~/.cache/golangci-lint/$$ver:/root/.cache -w /app golangci/golangci-lint:$$ver golangci-lint \\\n\trun --timeout 8m --fix ./...\nendif\n\t$(tools/protolint) lint --fix rpc || true\n\n.PHONY: check-all\ncheck-all: check-integration check-unit ## (QA) Run the test suite\n\n.PHONY: check-unit\ncheck-unit: build-deps $(tools/test-report) ## (QA) Run the test suite\n\tset -o pipefail\nifeq ($(GOOS),linux)\n\tCGO_ENABLED=$(CGO_ENABLED) go test -json -failfast -timeout=20m ./cmd/... ./pkg/... | $(tools/test-report)\nelse\n\tCGO_ENABLED=$(CGO_ENABLED) go test -json -failfast -timeout=20m ./pkg/... | $(tools/test-report)\nendif\n\n.PHONY: check-integration\nifeq ($(GOHOSTOS), linux)\ncheck-integration: client-image $(tools/test-report) $(tools/helm) ## (QA) Run the test suite\nelse\ncheck-integration: build-deps $(tools/test-report) $(tools/helm) ## (QA) Run the test suite\nendif\n\t# We run the test suite with TELEPRESENCE_LOGIN_DOMAIN set to localhost since that value\n\t# is only used for extensions. Therefore, we want to validate that our tests, and\n\t# telepresence, run without requiring any outside dependencies.\n\tset -o pipefail\n\tTELEPRESENCE_MAX_LOGFILES=300 TELEPRESENCE_LOGIN_DOMAIN=127.0.0.1 CGO_ENABLED=$(CGO_ENABLED) go test $(BUILD_TAGS) \\\n \t\t-count=1 -failfast -json -timeout=80m ./integration_test/... | $(tools/test-report)\n\n.PHONY: _login\n_login:\n\tdocker login --username \"$$TELEPRESENCE_REGISTRY_USERNAME\" --password \"$$TELEPRESENCE_REGISTRY_PASSWORD\"\n\n\n# Install\n# =======\n\n.PHONY: install\ninstall: build ## (Install) Installs the telepresence binary to $(bindir)\n\tinstall -Dm755 $(BINDIR)/telepresence $(bindir)/telepresence\n\n.PHONY: private-registry\nprivate-registry: $(tools/helm) ## (Test) Add a private docker registry to the current k8s cluster and make it available on localhost:5000.\n\tmkdir -p $(BUILDDIR)\n\t$(tools/helm) repo add twuni https://helm.twun.io\n\t$(tools/helm) repo update\n\t$(tools/helm) install docker-registry twuni/docker-registry\n\tkubectl apply -f k8s/private-reg-proxy.yaml\n\tkubectl rollout status -w daemonset/private-registry-proxy\n\tsleep 5\n\tkubectl wait --for=condition=ready pod --all\n\tkubectl port-forward daemonset/private-registry-proxy 5000:5000 > /dev/null &\n\n# Aliases\n# =======\n\n.PHONY: test save-image push-image\ntest:        check-all       ## (ZAlias) Alias for 'check-all'\nsave-image: save-tel2-image\npush-image: push-tel2-image\n\n.PHONY: push-test-images\npush-test-images: push-echo-server push-udp-echo\n\n.PHONY: push-echo-server\npush-echo-server:\n\t(cd integration_test/testdata/echo-server && \\\n \t\tdocker buildx build --platform=linux/amd64,linux/arm64 --push \\\n \t\t --tag ghcr.io/telepresenceio/echo-server:latest \\\n \t\t --tag ghcr.io/telepresenceio/echo-server:0.3.1 .)\n\n.PHONY: push-udp-echo\npush-udp-echo:\n\t(cd integration_test/testdata/udp-echo && \\\n\t\tdocker buildx build --platform=linux/amd64,linux/arm64 --push \\\n\t\t --tag ghcr.io/telepresenceio/udp-echo:latest \\\n\t\t --tag ghcr.io/telepresenceio/udp-echo:0.1.0 .)\n\n\nK8S_VERSION ?= $(shell go list -m k8s.io/client-go | awk '{print $$2}' | sed -e 's/v0./v1./')\ncharts/telepresence-oss/k8s-defs.json: go.mod\n\tcurl -o $@ --fail -L https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/$(K8S_VERSION)-standalone/_definitions.json\n\nbuild-deps: charts/telepresence-oss/k8s-defs.json\n"
  },
  {
    "path": "build-aux/maintenance.mk",
    "content": "\n\nupdate-dependencies: $(dir $(shell find . -name go.mod))\n\tfor dir in $?; do\\\n \t\t(cd $$dir && \\\n \t\t (go get -u ./... && \\\n \t\t   (grep -q gvisor.dev go.mod && go get -u gvisor.dev/gvisor@go) \\\n \t\t ) || \\\n \t\t go get -u .);\\\n \tdone\n\t$(MAKE) clobber generate check-unit\n\n"
  },
  {
    "path": "build-aux/pkg-installer/Distribution.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<installer-script minSpecVersion=\"1\" forVolumeCalculation=\"any\" authoringTool=\"com.apple.PackageMaker\">\n  <title>Telepresence __VERSION__</title>\n  <welcome file=\"welcome.rtf\"/>\n  <license file=\"license.rtf\"/>\n  <!-- === Always install CLI === -->\n  <pkg-ref id=\"io.telepresence.cli\"/>\n  <domains enable_anywhere=\"false\" enable_currentUserHome=\"false\" enable_localSystem=\"true\"/>\n\n  <!-- === Optional: Root Daemon === -->\n  <choices-outline>\n     <line choice=\"choice-cli\"/>\n     <line choice=\"choice-rootd\"/>\n  </choices-outline>\n\n  <choice id=\"choice-cli\"\n            title=\"Install CLI\"\n            description=\"Install Telepresence CLI.\"\n            selected=\"true\"\n            enabled=\"false\"\n            visible=\"false\">\n        <pkg-ref id=\"io.telepresence.cli\"/>\n  </choice>\n\n  <choice id=\"choice-rootd\"\n            title=\"Enable Root Daemon\"\n            description=\"Run telepresence rootd as a system service.\"\n            selected=\"true\"\n            start_selected=\"true\">\n        <pkg-ref id=\"io.telepresence.rootd\"/>\n  </choice>\n\n  <!-- === Packages === -->\n  <pkg-ref id=\"io.telepresence.cli\" auth=\"Root\">cli.pkg</pkg-ref>\n  <pkg-ref id=\"io.telepresence.rootd\" auth=\"Root\">rootd.pkg</pkg-ref>\n</installer-script>\n"
  },
  {
    "path": "build-aux/pkg-installer/build-pkg.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\n# Validate that VERSION is set and a valid SemVer\n[[ -z \"${VERSION}\" ]] && { echo \"Error: VERSION required\" >&2; exit 1; }\nif ! [[ \"$VERSION\" =~ ^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-([0-9A-Za-z\\.-]+))?(\\+([0-9A-Za-z\\.-]+))?$ ]]; then\n    echo \"Error: VERSION='$VERSION' is not a valid SemVer string.\" >&2\n    exit 1\nfi\n\n# Only run on Darwin. Other OSes are unlikely to have the pkgbuild and productbuild commands\nif [[ \"$(uname -s)\" != \"Darwin\" ]]; then\n    echo \"Error: This script only supports macOS (Darwin).\" >&2\n    exit 1\nfi\n\n# === Signing and Notarization Configuration ===\n# These environment variables enable code signing and notarization:\n#   MACOS_SIGN_APPLICATION - Developer ID Application certificate name (for codesign)\n#   MACOS_SIGN_INSTALLER   - Developer ID Installer certificate name (for productsign)\n#   MACOS_NOTARIZE_APPLE_ID - Apple ID for notarization\n#   MACOS_NOTARIZE_TEAM_ID  - Apple Developer Team ID\n#   MACOS_NOTARIZE_PASSWORD - App-specific password for notarization\n#\n# If signing variables are not set, packages will be built unsigned (suitable for local development).\n\nsign_binary() {\n    local binary=\"$1\"\n    if [[ -n \"${MACOS_SIGN_APPLICATION}\" ]]; then\n        echo \"Signing binary: $binary\"\n        codesign --force --options runtime --timestamp --sign \"${MACOS_SIGN_APPLICATION}\" \"$binary\"\n        codesign --verify --verbose \"$binary\"\n    fi\n}\n\nsign_package() {\n    local unsigned_pkg=\"$1\"\n    local signed_pkg=\"$2\"\n    if [[ -n \"${MACOS_SIGN_INSTALLER}\" ]]; then\n        echo \"Signing package: $unsigned_pkg -> $signed_pkg\"\n        productsign --sign \"${MACOS_SIGN_INSTALLER}\" \"$unsigned_pkg\" \"$signed_pkg\"\n        pkgutil --check-signature \"$signed_pkg\"\n    else\n        # No signing, just rename\n        mv \"$unsigned_pkg\" \"$signed_pkg\"\n    fi\n}\n\nnotarize_package() {\n    local pkg=\"$1\"\n    if [[ -n \"${MACOS_NOTARIZE_APPLE_ID}\" && -n \"${MACOS_NOTARIZE_TEAM_ID}\" && -n \"${MACOS_NOTARIZE_PASSWORD}\" ]]; then\n        echo \"Submitting package for notarization: $pkg\"\n        # Use --timeout to prevent indefinite waiting (15 minutes should be plenty)\n        xcrun notarytool submit \"$pkg\" \\\n            --apple-id \"${MACOS_NOTARIZE_APPLE_ID}\" \\\n            --team-id \"${MACOS_NOTARIZE_TEAM_ID}\" \\\n            --password \"${MACOS_NOTARIZE_PASSWORD}\" \\\n            --wait \\\n            --timeout 15m\n\n        echo \"Stapling notarization ticket to: $pkg\"\n        xcrun stapler staple \"$pkg\"\n        xcrun stapler validate \"$pkg\"\n    else\n        echo \"Skipping notarization (credentials not provided)\"\n    fi\n}\n\n# Allow ARCH override from environment (for CI cross-builds), otherwise detect\nif [[ -n \"${ARCH}\" ]]; then\n    arch=\"${ARCH}\"\nelse\n    raw_arch=$(uname -m)\n    case \"$raw_arch\" in\n        arm64)\n            arch=\"arm64\"\n            ;;\n        x86_64)\n            arch=\"amd64\"\n            ;;\n        *)\n            echo \"Error: Unsupported architecture: $raw_arch\" >&2\n            echo \"This script only supports arm64 (Apple Silicon) and x86_64 (Intel).\" >&2\n            exit 1\n            ;;\n    esac\nfi\n\nbuild_output=../../build-output\nmkdir -p \"$build_output\"\ntelepresence_binary=\"$build_output/telepresence-$VERSION\"\ntelepresence_url=\"https://github.com/telepresenceio/telepresence/releases/download/v${VERSION}/telepresence-darwin-${arch}\"\n\n# For CI builds, check if a local binary exists from the main build process\nlocal_binary=\"$build_output/bin/telepresence\"\nif [[ -f \"$local_binary\" ]] && file \"$local_binary\" | grep -q \"Mach-O\"; then\n  echo \"Using local build: $local_binary\"\n  cp \"$local_binary\" \"$telepresence_binary\"\nelif [[ ! -f \"$telepresence_binary\" ]]; then\n  # Download only if missing OR outdated\n  curl -L --fail --remote-time --output \"$telepresence_binary\" \"$telepresence_url\" || exit 1\nelse\n  curl -L --fail --remote-time --output \"$telepresence_binary\" --time-cond \"$telepresence_binary\" \"$telepresence_url\" || exit 1\nfi\n\n# === Build CLI-only package ===\ncli_payload=\"$build_output/cli/Payload\"\nmkdir -p \"$cli_payload/usr/local/bin\"\ncp \"$telepresence_binary\" \"$cli_payload/usr/local/bin/telepresence\"\nchmod +x \"$cli_payload/usr/local/bin/telepresence\"\nsign_binary \"$cli_payload/usr/local/bin/telepresence\"\n\ncp uninstall \"$cli_payload/usr/local/bin/telepresence-uninstall\"\nchmod +x \"$cli_payload/usr/local/bin/telepresence-uninstall\"\n\nproduct=\"$build_output/product\"\nmkdir -p \"$product\"\nsed \"s|__VERSION__|$VERSION|g\" Distribution.xml > \"$product/Distribution.xml\"\n\npkgbuild --identifier io.telepresence.cli \\\n         --version \"$VERSION\" \\\n         --root \"$cli_payload\" \\\n         --install-location / \\\n         \"$product/cli.pkg\"\n\n# === Build Rootd package ===\nrootd_payload=\"$build_output/rootd/Payload\"\nmkdir -p \"$rootd_payload/usr/local/bin\"\ncp telepresence-rootd \"$rootd_payload/usr/local/bin\"\nchmod +x \"$rootd_payload/usr/local/bin/telepresence-rootd\"\n\nmkdir -p \"$rootd_payload/Library/LaunchDaemons\"\ncp io.telepresence.rootd.plist \"$rootd_payload/Library/LaunchDaemons/\"\n\nmkdir -p \"$rootd_payload/etc/newsyslog.d\"\ncp syslog.conf \"$rootd_payload/etc/newsyslog.d/telepresence-rootd.conf\"\n\nrootd_scripts=\"$build_output/rootd/Scripts\"\nmkdir -p \"$rootd_scripts\"\ncp postinstall \"$rootd_scripts/postinstall\"\nchmod +x \"$rootd_scripts/postinstall\"\n\npkgbuild --identifier io.telepresence.rootd \\\n         --version \"$VERSION\" \\\n         --root \"$rootd_payload\" \\\n         --scripts \"$rootd_scripts\" \\\n         --install-location / \\\n         \"$product/rootd.pkg\"\n\nresources=\"$build_output/resources\"\nmkdir -p \"$resources\"\ncp welcome.rtf \"$resources/welcome.rtf\"\ntextutil -convert rtf -stdin -stdout < ../../LICENSE > \"$resources/license.rtf\"\n\nproductbuild --distribution \"$product/Distribution.xml\" \\\n             --package-path \"$product\" \\\n             --resources \"$resources\" \\\n             --version \"$VERSION\" \\\n             \"$build_output/Telepresence-unsigned.pkg\"\n\n# Sign the package (or rename if no signing certificate)\nsign_package \"$build_output/Telepresence-unsigned.pkg\" \"$build_output/Telepresence.pkg\"\n\n# Notarize the signed package (if credentials are provided)\nnotarize_package \"$build_output/Telepresence.pkg\"\n\nrm -rf cli rootd resources product\n"
  },
  {
    "path": "build-aux/pkg-installer/io.telepresence.rootd.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n  <key>Label</key>\n  <string>io.telepresence.rootd</string>\n\n  <key>ProgramArguments</key>\n  <array>\n    <string>/usr/local/bin/telepresence</string>\n    <string>rootd</string>\n    <string>--logfile</string>  <string>std</string>\n    <string>--config</string>   <string>/Library/Application Support/telepresence/config.yml</string>\n    <string>--address</string>  <string>:4037</string>\n    <string>--managed</string>\n  </array>\n\n  <key>RunAtLoad</key><true/>\n  <key>KeepAlive</key><true/>\n\n  <key>StandardOutPath</key>\n  <string>/var/log/telepresence-rootd.log</string>\n  <key>StandardErrorPath</key>\n  <string>/var/log/telepresence-rootd.log</string>\n\n  <key>ProcessType</key>\n  <string>Interactive</string>\n\n  <key>WorkingDirectory</key>\n  <string>/Library/Caches/telepresence</string>\n\n  <!-- === Environment === -->\n  <key>EnvironmentVariables</key>\n  <dict>\n      <key>PATH</key>\n      <string>/usr/local/bin:/usr/bin:/bin</string>\n      <key>HOME</key>\n      <string>/var/root</string>\n  </dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "build-aux/pkg-installer/postinstall",
    "content": "#!/bin/bash\nset -e\n\nmkdir -p \"/Library/Application Support/telepresence\"\nmkdir -p \"/Library/Caches/telepresence\"\ntouch \"/Library/Application Support/telepresence/config.yml\"\n\nchown -R root:wheel \\\n    \"/Library/Application Support/telepresence\" \\\n    \"/Library/Caches/telepresence\"\n\nchmod 755 \"/Library/Application Support/telepresence\"\nchmod 755 \"/Library/Caches/telepresence\"\nchmod 644 \"/Library/Application Support/telepresence/config.yml\"\n\necho \"Starting Telepresence Root Daemon...\"\n/bin/launchctl load -w /Library/LaunchDaemons/io.telepresence.rootd.plist\n\necho \"Telepresence Root Daemon is running.\"\necho \"Control with: telepresence-rootd start|stop|restart\"\n"
  },
  {
    "path": "build-aux/pkg-installer/syslog.conf",
    "content": "# /etc/newsyslog.d/telepresence-rootd.conf\n# Rotate telepresence-rootd.log daily, keep 7 days, compress, restart daemon\n\n/var/log/telepresence-rootd.log {\n    daily\n    rotate 7\n    compress\n    missingok\n}\n"
  },
  {
    "path": "build-aux/pkg-installer/telepresence-rootd",
    "content": "#!/bin/bash\nACTION=\"$1\"\nif [[ \"$ACTION\" != \"start\" && \"$ACTION\" != \"stop\" && \"$ACTION\" != \"restart\" ]]; then\n    echo \"Usage: $0 start|stop|restart\"\n    exit 1\nfi\ncase \"$ACTION\" in\n    start)   sudo /bin/launchctl load -w /Library/LaunchDaemons/io.telepresence.rootd.plist ;;\n    stop)    sudo /bin/launchctl unload -w /Library/LaunchDaemons/io.telepresence.rootd.plist ;;\n    restart) sudo /bin/launchctl unload -w /Library/LaunchDaemons/io.telepresence.rootd.plist 2>/dev/null || true; sudo /bin/launchctl load -w /Library/LaunchDaemons/io.telepresence.rootd.plist ;;\nesac\necho \"Telepresence Daemon $ACTION requested.\"\n"
  },
  {
    "path": "build-aux/pkg-installer/uninstall",
    "content": "#!/usr/bin/env bash\necho \"Uninstalling Telepresence...\"\nsudo /bin/launchctl unload -w /Library/LaunchDaemons/io.telepresence.rootd.plist 2>/dev/null || true\nsudo rm -f /usr/local/bin/telepresence\nsudo rm -f /usr/local/bin/telepresence-rootd\nsudo rm -f /Library/LaunchDaemons/io.telepresence.rootd.plist\nsudo rm -rf \"/Library/Application Support/telepresence\"\nsudo rm -rf \"/Library/Caches/telepresence\"\necho \"Uninstalled.\"\n"
  },
  {
    "path": "build-aux/pkg-installer/welcome.rtf",
    "content": "{\\rtf1\\ansi\\ansicpg1252\\cocoartf2822\n\\cocoatextscaling0\\cocoaplatform0{\\fonttbl\\f0\\fswiss\\fcharset0 Helvetica-Bold;\\f1\\fswiss\\fcharset0 Helvetica;\\f2\\froman\\fcharset0 Times-Roman;\n\\f3\\fmodern\\fcharset0 CourierNewPSMT;}\n{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;\\red255\\green255\\blue255;\\red255\\green255\\blue255;\n}\n{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;\\cssrgb\\c100000\\c100000\\c100000\\c0;\\cssrgb\\c100000\\c100000\\c99985\\c0;\n}\n\\paperw12240\\paperh15840\\vieww36540\\viewh8400\\viewkind0\n\\pard\\sa240\\pardirnatural\\partightenfactor0\n\n\\f0\\b\\fs28 \\cf2 \\cb3 Install CLI only or CLI + system daemon\n\\f1\\b0 \\\n\\pard\\sa240\\pardirnatural\\partightenfactor0\n\n\\fs24 \\cf2 \\cb4 Click the \"\n\\f2 \\cb4 Customise\"\n\\f1 \\cb4  button in the \"Installation Type\" step to configure the telepresence root daemon as a system daemon.\\cb3 \\\n\\pard\\sa240\\pardirnatural\\partightenfactor0\n\n\\fs22 \\cf2 When the system daemon is running, a \n\\f3 telepresence connect\n\\f1  will no longer need to use \n\\f3 sudo\n\\f1 .}"
  },
  {
    "path": "build-aux/prelude.mk",
    "content": "# Copyright 2020-2021 Datawire.  All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 file deals with baseline 'Makefile' utilities, without doing\n# anything specific to Telepresence.\n\n# Delete implicit rules not used here (clutters debug output).\nMAKEFLAGS += --no-builtin-rules\n\n# Turn off .INTERMEDIATE file removal by marking all files as\n# .SECONDARY.  .INTERMEDIATE file removal is a space-saving hack from\n# a time when drives were small; on modern computers with plenty of\n# storage, it causes nothing but headaches.\n#\n# https://news.ycombinator.com/item?id=16486331\n.SECONDARY:\n\n# If a recipe errors, remove the target it was building.  This\n# prevents outdated/incomplete results of failed runs from tainting\n# future runs.  The only reason .DELETE_ON_ERROR is off by default is\n# for historical compatibility.\n#\n# If for some reason this behavior is not desired for a specific\n# target, mark that target as .PRECIOUS.\n.DELETE_ON_ERROR:\n\n# Be silent\n$(VERBOSE).SILENT:\n\n# Add a rule to generate `make help` output from comments in the\n# Makefiles.\n.PHONY: help\nhelp:  ## (ZSupport) Show this message\n\t@echo 'Usage: [VARIABLE=VALUE...] $(MAKE) [TARGETS...]'\n\t@echo\n\t@echo VARIABLES:\n\t@{ $(foreach varname,$(shell sed -n '/[?]=/{ s/[ ?].*//; s/^/  /; p; }' $(sort $(abspath $(MAKEFILE_LIST)))),printf '%s = %s\\n' '$(varname)' '$($(varname))';) } | column -t | sed 's/^/  /'\n\t@echo\n\t@echo TARGETS:\n\t@sed -En 's/^([^:]*):[^#]*## *(\\([^)]*\\))? */\\2\t\\1\t/p' $(sort $(abspath $(MAKEFILE_LIST))) | sed 's/^\t/($(or $(NAME),this project))&/' | column -t -s '\t' | sed 's/^/  /' | sort\n\t@echo\n\t@echo \"See DEVELOPING.md for more information\"\n"
  },
  {
    "path": "build-aux/systemd-installer/.gitignore",
    "content": "nfpm.yaml\n"
  },
  {
    "path": "build-aux/systemd-installer/build-packages.sh",
    "content": "#!/bin/bash\nset -e\n\n# Build .deb and .rpm packages using nfpm\n\n# Validate required environment variables\n[[ -z \"${VERSION}\" ]] && { echo \"Error: VERSION required\" >&2; exit 1; }\n[[ -z \"${ARCH}\" ]] && { echo \"Error: ARCH required (amd64 or arm64)\" >&2; exit 1; }\n\n# Only run on Linux\nif [[ \"$(uname -s)\" != \"Linux\" ]]; then\n    echo \"Error: This script only supports Linux.\" >&2\n    exit 1\nfi\n\n# Parse version for package managers (RPM doesn't allow hyphens in version)\n# Convert 2.27.0-test.4 to version=2.27.0 release=test.4\nif [[ \"$VERSION\" =~ ^([0-9]+\\.[0-9]+\\.[0-9]+)-(.+)$ ]]; then\n    PKG_VERSION=\"${BASH_REMATCH[1]}\"\n    PKG_RELEASE=\"${BASH_REMATCH[2]}\"\nelse\n    PKG_VERSION=\"$VERSION\"\n    PKG_RELEASE=\"1\"\nfi\nexport PKG_VERSION PKG_RELEASE\n\necho \"Building packages: version=${PKG_VERSION}, release=${PKG_RELEASE}, arch=${ARCH}\"\n\nscript_dir=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nbuild_output=\"$(cd \"${script_dir}/../..\" && pwd)/build-output\"\nmkdir -p \"$build_output/release\"\n\necho \"script_dir=$script_dir\"\necho \"build_output=$build_output\"\n\n# For CI builds, check if a local binary exists from the main build process\nlocal_binary=\"${build_output}/bin/telepresence\"\necho \"Checking for local binary at: $local_binary\"\nif [[ -f \"$local_binary\" ]] && file \"$local_binary\" | grep -q \"ELF\"; then\n    echo \"Using local build: $local_binary\"\n    BINDIR=\"${build_output}/bin\"\nelse\n    # Download from GitHub releases\n    echo \"Downloading telepresence v${VERSION} for linux-${ARCH}...\"\n    mkdir -p \"${build_output}/bin\"\n    curl -L --fail --remote-time \\\n        -o \"${build_output}/bin/telepresence\" \\\n        \"https://github.com/telepresenceio/telepresence/releases/download/v${VERSION}/telepresence-linux-${ARCH}\"\n    chmod +x \"${build_output}/bin/telepresence\"\n    BINDIR=\"${build_output}/bin\"\nfi\n\nexport BINDIR\necho \"BINDIR=$BINDIR\"\necho \"Binary exists: $(ls -la \"$BINDIR/telepresence\" 2>&1)\"\n\ncd \"$script_dir\"\n\n# Map arch to nfpm format\ncase \"$ARCH\" in\n    amd64)\n        nfpm_arch=\"amd64\"\n        ;;\n    arm64)\n        nfpm_arch=\"arm64\"\n        ;;\n    *)\n        echo \"Error: Unsupported architecture: $ARCH\" >&2\n        exit 1\n        ;;\nesac\n\necho \"Building packages for linux-${ARCH}, version ${VERSION}...\"\n\n# Export all variables needed by nfpm.yaml.in and preprocess with envsubst\nexport ARCH=\"$nfpm_arch\"\n# PKG_VERSION, PKG_RELEASE, and BINDIR are already exported above\n\necho \"Environment for nfpm: ARCH=$ARCH PKG_VERSION=$PKG_VERSION PKG_RELEASE=$PKG_RELEASE BINDIR=$BINDIR\"\nenvsubst < nfpm.yaml.in > nfpm.yaml\n\n# Build .deb package\necho \"Building .deb package...\"\nnfpm package \\\n    --config nfpm.yaml \\\n    --packager deb \\\n    --target \"${build_output}/release/telepresence-${VERSION}-linux-${ARCH}.deb\"\n\n# Build .rpm package\necho \"Building .rpm package...\"\nnfpm package \\\n    --config nfpm.yaml \\\n    --packager rpm \\\n    --target \"${build_output}/release/telepresence-${VERSION}-linux-${ARCH}.rpm\"\n\necho \"\"\necho \"Packages built successfully:\"\nls -la \"${build_output}/release/\"*.deb \"${build_output}/release/\"*.rpm 2>/dev/null || true"
  },
  {
    "path": "build-aux/systemd-installer/config.yml",
    "content": "# Telepresence root daemon configuration\n# See https://www.telepresence.io/docs/reference/config for all options\n\nlogLevels:\n  rootDaemon: info"
  },
  {
    "path": "build-aux/systemd-installer/nfpm.yaml.in",
    "content": "# nfpm configuration for building .deb and .rpm packages\n# Documentation: https://nfpm.goreleaser.com/configuration/\n\nname: telepresence\narch: ${ARCH}\nplatform: linux\nversion: ${PKG_VERSION}\nrelease: ${PKG_RELEASE}\nmaintainer: https://github.com/telepresenceio/telepresence\ndescription: |\n  Telepresence: fast, local development for Kubernetes.\n  Telepresence connects your local workstation to a remote Kubernetes cluster,\n  allowing you to run services locally while accessing cluster resources.\nvendor: Telepresence.io\nhomepage: https://telepresence.io/\nlicense: Apache-2.0\nsection: net\n\n# Package dependencies\ndepends:\n  - fuse3\nrecommends:\n  - sshfs\n\ncontents:\n  # Main binary\n  - src: ${BINDIR}/telepresence\n    dst: /usr/local/bin/telepresence\n    file_info:\n      mode: 0755\n\n  # Systemd service file\n  - src: telepresence-rootd.service\n    dst: /usr/lib/systemd/system/telepresence-rootd.service\n    file_info:\n      mode: 0644\n\n  # Default configuration\n  - src: config.yml\n    dst: /etc/telepresence/config.yml\n    type: config|noreplace\n    file_info:\n      mode: 0644\n\n  # Uninstall script\n  - src: uninstall.sh\n    dst: /usr/local/bin/telepresence-uninstall\n    file_info:\n      mode: 0755\n\n  # Create required directories\n  - dst: /var/cache/telepresence\n    type: dir\n    file_info:\n      mode: 0700\n      owner: root\n      group: root\n\nscripts:\n  postinstall: ./postinstall.sh\n  preremove: ./preremove.sh\n  postremove: ./postremove.sh\n\n# RPM-specific settings\nrpm:\n  group: Applications/Internet\n  summary: Fast, local development for Kubernetes\n"
  },
  {
    "path": "build-aux/systemd-installer/postinstall.sh",
    "content": "#!/bin/bash\nset -e\n\n# Create required directories\nmkdir -p /var/cache/telepresence/rootd\nmkdir -p /etc/telepresence\nchmod 755 /var/cache/telepresence\nchmod 755 /var/cache/telepresence/rootd\n\n# Reload systemd to pick up the new service file (may fail in containers)\nsystemctl daemon-reload 2>/dev/null || true\n\n# Enable and start the service\nsystemctl enable telepresence-rootd.service 2>/dev/null || true\nsystemctl start telepresence-rootd.service 2>/dev/null || true\n\necho \"\"\necho \"Telepresence has been installed successfully!\"\necho \"\"\necho \"The root daemon service has been enabled and started.\"\necho \"\"\necho \"To check service status:\"\necho \"  sudo systemctl status telepresence-rootd\"\necho \"\"\necho \"To view service logs:\"\necho \"  sudo journalctl -u telepresence-rootd\"\necho \"\""
  },
  {
    "path": "build-aux/systemd-installer/postremove.sh",
    "content": "#!/bin/bash\nset -e\n\n# Reload systemd to forget about the removed service\nsystemctl daemon-reload || true\n\necho \"\"\necho \"Telepresence has been removed.\"\necho \"\"\necho \"Note: Configuration in /etc/telepresence and logs in /var/log/telepresence\"\necho \"have been preserved. Remove them manually if no longer needed.\"\necho \"\""
  },
  {
    "path": "build-aux/systemd-installer/preremove.sh",
    "content": "#!/bin/bash\nset -e\n\n# Stop the service if running\nif systemctl is-active --quiet telepresence-rootd.service 2>/dev/null; then\n    echo \"Stopping telepresence-rootd service...\"\n    systemctl stop telepresence-rootd.service || true\nfi\n\n# Disable the service\nif systemctl is-enabled --quiet telepresence-rootd.service 2>/dev/null; then\n    echo \"Disabling telepresence-rootd service...\"\n    systemctl disable telepresence-rootd.service || true\nfi"
  },
  {
    "path": "build-aux/systemd-installer/telepresence-rootd.service",
    "content": "[Unit]\nDescription=Telepresence root daemon (rootd)\nDocumentation=https://telepresence.io/\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/telepresence rootd \\\n    --logfile managed \\\n    --config /etc/telepresence/config.yml \\\n    --address :4037 \\\n    --managed\n\n# Runs as root (LaunchDaemon equivalent)\nUser=root\nGroup=root\n\n# Linux-standard directories\nWorkingDirectory=/var/cache/telepresence\n# Creates /run/telepresence for runtime socket\nRuntimeDirectory=telepresence\nRuntimeDirectoryMode=0755\n\n# Restart behaviour = KeepAlive<true/> + RunAtLoad<true/>\nRestart=always\nRestartSec=3\n\n# Logging (use journalctl -u telepresence-rootd to view logs)\nStandardOutput=journal\nStandardError=journal\n\n# Environment\nEnvironment=PATH=/usr/local/bin:/usr/bin:/bin\n# Telepresence sometimes expects a writable HOME\nEnvironment=HOME=/var/root\n\n# Security hardening (recommended)\nNoNewPrivileges=yes\nProtectSystem=strict\nReadWritePaths=/var/cache/telepresence\nProtectHome=read-only\nPrivateTmp=yes\nRestrictSUIDSGID=yes\nRemoveIPC=yes\nRestrictNamespaces=yes\nProtectHostname=yes\nProtectKernelTunables=yes\nProtectKernelModules=yes\nProtectControlGroups=yes\n\n[Install]\nWantedBy=multi-user.target"
  },
  {
    "path": "build-aux/systemd-installer/uninstall.sh",
    "content": "#!/bin/bash\nset -e\n\necho \"Uninstalling Telepresence...\"\n\n# Stop and disable the service\nif systemctl is-active --quiet telepresence-rootd.service 2>/dev/null; then\n    echo \"Stopping telepresence-rootd service...\"\n    sudo systemctl stop telepresence-rootd.service\nfi\n\nif systemctl is-enabled --quiet telepresence-rootd.service 2>/dev/null; then\n    echo \"Disabling telepresence-rootd service...\"\n    sudo systemctl disable telepresence-rootd.service\nfi\n\n# Remove files\necho \"Removing files...\"\nsudo rm -f /usr/local/bin/telepresence\nsudo rm -f /usr/local/bin/telepresence-uninstall\nsudo rm -f /usr/lib/systemd/system/telepresence-rootd.service\n\n# Reload systemd\nsudo systemctl daemon-reload\n\necho \"\"\necho \"Telepresence has been uninstalled.\"\necho \"\"\necho \"The following directories have been preserved:\"\necho \"  /etc/telepresence     - configuration\"\necho \"  /var/cache/telepresence - cache data\"\necho \"  /var/log/telepresence - logs\"\necho \"\"\necho \"Remove them manually if no longer needed:\"\necho \"  sudo rm -rf /etc/telepresence /var/cache/telepresence /var/log/telepresence\"\necho \"\""
  },
  {
    "path": "build-aux/tools.mk",
    "content": "# Copyright 2020-2021 Datawire.  All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF 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 file deals with installing programs used by the build.\n\nTOOLSDIR=tools\nTOOLSBINDIR=$(TOOLSDIR)/bin\nTOOLSSRCDIR=$(TOOLSDIR)/src\n\nGOHOSTOS ?= $(shell go env GOHOSTOS)\nGOHOSTARCH ?= $(shell go env GOHOSTARCH)\n\nGOOS?=$(shell go env GOOS)\nGOARCH?=$(shell go env GOARCH)\n\nexport PATH := $(abspath $(TOOLSBINDIR)):$(PATH)\n\nclobber: clobber-tools\n\n.PHONY: clobber-tools\nclobber-tools:\n\trm -rf $(TOOLSBINDIR) $(TOOLSDIR)/include $(TOOLSDIR)/*.*\n\n\n# Protobuf compiler\n# =================\n#\n# Install protoc under $TOOLSDIR. A protoc that is already installed locally\n# cannot be trusted since this must be the exact same version as used when\n# running CI. If it isn't, the generate-check will fail.\nPROTOC_VERSION=30.2\nifeq ($(GOHOSTARCH),arm64)\n  PROTOC_ARCH=aarch_64\nelse ifeq ($(GOHOSTARCH),amd64)\n  PROTOC_ARCH=x86_64\nelse\n  PROTOC_ARCH=$(GOHOSTARCH)\nendif\n\nifeq ($(GOHOSTOS),windows)\n  PROTOC_OS_ARCH=win64\n  EXE=.exe\nelse\n  PROTOC_OS_ARCH=$(patsubst darwin,osx,$(GOHOSTOS))-$(PROTOC_ARCH)\n  EXE=\nendif\ntools/protoc = $(TOOLSBINDIR)/protoc$(EXE)\n\nPROTOC_ZIP=protoc-$(PROTOC_VERSION)-$(PROTOC_OS_ARCH).zip\n$(TOOLSDIR)/$(PROTOC_ZIP):\n\tmkdir -p $(@D)\n\tcurl -sfL https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC_ZIP) -o $@\n%/bin/protoc$(EXE) %/include %/readme.txt: %/$(PROTOC_ZIP)\n\tcd $* && unzip -q -o -DD $(<F)\n\n# Protobuf linter\n# ===============\n#\ntools/protolint = $(TOOLSBINDIR)/protolint$(EXE)\nPROTOLINT_VERSION=0.53.0\nPROTOLINT_TGZ=protolint_$(PROTOLINT_VERSION)_$(GOHOSTOS)_$(GOHOSTARCH).tar.gz\n$(TOOLSDIR)/$(PROTOLINT_TGZ):\n\tmkdir -p $(@D)\n\tcurl -sfL https://github.com/yoheimuta/protolint/releases/download/v$(PROTOLINT_VERSION)/$(PROTOLINT_TGZ) -o $@\n%/bin/protolint$(EXE) %/bin/protoc-gen-protolint$(EXE): %/$(PROTOLINT_TGZ)\n\tmkdir -p $(@D)\n\ttar -C $(@D) -zxmf $< protolint$(EXE) protoc-gen-protolint$(EXE)\n\n# Test reporter\n# ==========\n#\ntools/test-report = $(TOOLSBINDIR)/test-report$(EXE)\n$(TOOLSBINDIR)/test-report$(EXE): $(TOOLSSRCDIR)/test-report/*.go $(TOOLSSRCDIR)/test-report/go.* $(TOOLSSRCDIR)/test-report/go.sum\n\tcd $(<D) && GOOS= GOARCH= go build -o $(abspath $@) *.go\n\n# TOC generator\n# ==========\n#\ntools/tocgen = $(TOOLSBINDIR)/tocgen$(EXE)\n$(TOOLSBINDIR)/tocgen$(EXE): $(TOOLSSRCDIR)/tocgen/*.go\n\tcd $(<D) && GOOS= GOARCH= go build -o $(abspath $@) *.go\n\n# Release Notes generator\n# ==========\n#\ntools/relnotesgen = $(TOOLSBINDIR)/relnotesgen$(EXE)\n$(TOOLSBINDIR)/relnotesgen$(EXE): $(TOOLSSRCDIR)/relnotesgen/**/*.go $(TOOLSSRCDIR)/relnotesgen/relnotes/relnotes.* $(TOOLSSRCDIR)/relnotesgen/go.sum\n\t(cd $(TOOLSSRCDIR)/relnotesgen && GOOS= GOARCH= go build) && mv $(TOOLSSRCDIR)/relnotesgen/relnotesgen $(TOOLSBINDIR)\n\n# YAML to JSON\n# ==========\n#\ntools/y2j = $(TOOLSBINDIR)/y2j$(EXE)\n$(TOOLSBINDIR)/y2j$(EXE): $(TOOLSSRCDIR)/y2j/*.go $(TOOLSSRCDIR)/y2j/go.sum\n\t(cd $(TOOLSSRCDIR)/y2j && GOOS= GOARCH= go build) && mv $(TOOLSSRCDIR)/y2j/y2j $(TOOLSBINDIR)\n\n# Shellcheck\n# ==========\n#\nifneq ($(GOHOSTOS),windows)\ntools/shellcheck = $(TOOLSBINDIR)/shellcheck\nSHELLCHECK_VERSION=0.8.0\nSHELLCHECK_ARCH=$(shell uname -m)\n# shellcheck uses the same binary on Intel and Apple Silicon macs\nifeq ($(GOHOSTOS),darwin)\nSHELLCHECK_ARCH=x86_64\nendif\nSHELLCHECK_TXZ = https://github.com/koalaman/shellcheck/releases/download/v$(SHELLCHECK_VERSION)/shellcheck-v$(SHELLCHECK_VERSION).$(GOHOSTOS).$(SHELLCHECK_ARCH).tar.xz\n$(TOOLSDIR)/$(notdir $(SHELLCHECK_TXZ)):\n\tmkdir -p $(@D)\n\tcurl -sfL $(SHELLCHECK_TXZ) -o $@\n%/bin/shellcheck: %/$(notdir $(SHELLCHECK_TXZ))\n\tmkdir -p $(@D)\n\ttar -C $(@D) -Jxmf $< --strip-components=1 shellcheck-v$(SHELLCHECK_VERSION)/shellcheck\nendif\n\n# Helm\n# ====\n#\ntools/helm = $(TOOLSBINDIR)/helm$(EXE)\nHELM_VERSION=$(shell go mod edit -json | jq -r '.Require[] | select (.Path == \"helm.sh/helm/v3\") | .Version')\nHELM_TGZ = https://get.helm.sh/helm-$(HELM_VERSION)-$(GOHOSTOS)-$(GOHOSTARCH).tar.gz\n$(TOOLSDIR)/$(notdir $(HELM_TGZ)):\n\tmkdir -p $(@D)\n\tcurl -sfL $(HELM_TGZ) -o $@\n%/bin/helm$(EXE): %/$(notdir $(HELM_TGZ))\n\tmkdir -p $(@D)\n\ttar -C $(@D) -zxmf $< --strip-components=1 $(GOHOSTOS)-$(GOHOSTARCH)/helm$(EXE)\n\n# `go get`-able things\n# ====================\n#\n# Install the all under $TOOLSDIR. Versions that are already in $GOBIN\n# cannot be trusted since this must be the exact same version as used\n# when running CI. If it isn't the generate-check will fail.\n#\n# Instead of having \"VERSION\" variables here, the versions are\n# controlled by `tools/src/${thing}/go.mod` files.  Having those in\n# separate per-tool go.mod files avoids conflicts between tools and\n# avoid them polluting our main go.mod file.\ntools/protoc-gen-go      = $(TOOLSBINDIR)/protoc-gen-go$(EXE)\ntools/protoc-gen-go-grpc = $(TOOLSBINDIR)/protoc-gen-go-grpc$(EXE)\ntools/go-mkopensource    = $(TOOLSBINDIR)/go-mkopensource$(EXE)\ntools/test-report        = $(TOOLSBINDIR)/test-report$(EXE)\ntools/y2j                = $(TOOLSBINDIR)/y2j$(EXE)\n$(TOOLSBINDIR)/%$(EXE): $(TOOLSSRCDIR)/%/pin.go | $(TOOLSSRCDIR)/%/go.sum\n\tcd $(<D) && GOOS= GOARCH= go build -o $(abspath $@) $$(sed -En 's,^import \"(.*)\".*,\\1,p' pin.go)\n\n$(TOOLSSRCDIR)/%/go.sum: $(TOOLSSRCDIR)/%/go.mod\n\tcd $(<D) && go mod tidy"
  },
  {
    "path": "build-aux/unparsable-packages.yaml",
    "content": "# Packages whose licenses cannot be auto-detected by go-mkopensource.\n# Format: package-import-path: [list of SPDX license identifiers]\n"
  },
  {
    "path": "build-aux/winmake.bat",
    "content": "echo off\nsetlocal\nmkdir .wintools\nmkdir .gocache\n\nif \"%TELEPRESENCE_REGISTRY%\" == \"\" (\n    echo \"Please define a %%TELEPRESENCE_REGISTRY%% environment variable. It must be a docker repo to which you have logged in via Docker Desktop\"\n    exit \\b\n)\n\nif not exist .wintools\\jq.exe (\n    curl -L https://github.com/stedolan/jq/releases/download/jq-1.6/jq-win64.exe --output .\\.wintools\\jq.exe\n)\n\ndocker build . -f .\\build-aux\\docker\\images\\Dockerfile.winbuild -t tel2-winbuild || exit \\b\n\nset pwd=%~dp0\nset pwd=%pwd:~3%\nset pwd=%pwd:\\=/%\n\nset drive=%~dp0\nset drive=%drive:~0,1%\n\n@REM It's really cool how there's no `backticks` in batch scripts so you have to use a for loop to set variables\n@REM Note also that we use a temp file because otherwise the jq command would have to have even more weird escape sequences\ndocker-credential-desktop.exe list | .\\.wintools\\jq.exe -r \". as $r | keys[] | select($r[.] == \\\"%TELEPRESENCE_REGISTRY%\\\") | sub(\\\"https://(?^<h^>[^^/]*)/.*\\\"; \\\"\\(.h)\\\")\" > .wintools\\docker-host\nfor /f \"delims=\" %%i in (.wintools\\docker-host) do set TELEPRESENCE_REGISTRY_HOST=%%i\ndel .wintools\\docker-host\n\nfor /f \"delims=\" %%i in ('echo %TELEPRESENCE_REGISTRY_HOST% ^| docker-credential-desktop.exe get ^| .wintools\\jq.exe -r .Username') do set \"TELEPRESENCE_REGISTRY_USERNAME=%%i\"\nfor /f \"delims=\" %%i in ('echo %TELEPRESENCE_REGISTRY_HOST% ^| docker-credential-desktop.exe get ^| .wintools\\jq.exe -r .Secret') do set \"TELEPRESENCE_REGISTRY_PASSWORD=%%i\"\n\ndocker run --rm ^\n    -v /host_mnt/%drive%/%pwd%:/source ^\n    -v //var/run/docker.sock:/var/run/docker.sock ^\n    -w /source ^\n    -e GOOS=windows ^\n    -e GOCACHE=/source/.gocache ^\n    -e GOARCH ^\n    -e TELEPRESENCE_REGISTRY ^\n    -e TELEPRESENCE_REGISTRY_USERNAME ^\n    -e TELEPRESENCE_REGISTRY_PASSWORD ^\n    -e TELEPRESENCE_VERSION ^\n    -e KO_DOCKER_REPO ^\n    tel2-winbuild ^\n    make _login %*\n"
  },
  {
    "path": "build-aux/wix-installer/Dialogs_en-us.wxl",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<WixLocalization xmlns=\"http://wixtoolset.org/schemas/v4/wxl\" Culture=\"en-US\" Codepage=\"1252\">\n  <String Id=\"SetupTypeDlgDescription\"\n      Value=\"Install Command Line Interface and optionally the Network Service\"/>\n  <String Id=\"SetupTypeDlgTypicalText\"\n      Value=\"Only install the Telepresence CLI. This means that a telepresence connect will require admin privileges unless the --docker flag is used.\"/>\n  <String Id=\"SetupTypeDlgCustomText\" Value=\"Choose what to install and where to install it.\"/>\n  <String Id=\"SetupTypeDlgCompleteText\"\n      Value=\"Install both the Telepresence CLI and the Telepresence Network System Service. Removes the need for admin privileges when using the CLI.\"/>\n</WixLocalization>\n"
  },
  {
    "path": "build-aux/wix-installer/MainPackage.wxs",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\"\n    xmlns:ui=\"http://wixtoolset.org/schemas/v4/wxs/ui\">\n  <Package Id=\"MainPackage\"\n      Name=\"Telepresence Components\"\n      Manufacturer=\"TelepresenceIO\"\n      Version=\"$(var.BundleVersion)\">\n\n    <MajorUpgrade DowngradeErrorMessage=\"A newer version is already installed.\"/>\n    <Property Id=\"PORT\" Secure=\"yes\" Value=\"4037\"/>\n    <Property Id=\"PPROFPORT\" Secure=\"yes\" Value=\"0\"/>\n    <Property Id=\"LOGLEVEL\" Secure=\"yes\" Value=\"info\"/>\n\n    <StandardDirectory Id=\"ProgramFiles64Folder\">\n      <Directory Id=\"INSTALLDIR\" Name=\"Telepresence\"/>\n      <Directory Id=\"SSHFSFOLDER\" Name=\"SSHFS-Win\">\n        <Directory Id=\"SSHFSBIN\" Name=\"bin\"/>\n      </Directory>\n    </StandardDirectory>\n\n    <StandardDirectory Id=\"CommonAppDataFolder\">\n      <Directory Id=\"CONFIGDIR\" Name=\"Telepresence\">\n        <Directory Id=\"LOGDIR\" Name=\"Logs\"/>\n        <Directory Id=\"CACHEDIR\" Name=\"Cache\">\n          <Directory Id=\"ROOTD\" Name=\"rootd\"/>\n        </Directory>\n      </Directory>\n    </StandardDirectory>\n\n    <ComponentGroup Id=\"TelepresenceFiles\">\n      <Component Id=\"Cmp_RemoveSSH\" Guid=\"3b05194e-3065-4f65-b184-5319eaeed41e\" Directory=\"SSHFSBIN\">\n        <!-- Remove the ssh.exe installed by the SSHFS-Win package. It conflicts with a more recent OpenSSH that Windows bundles by default -->\n        <RemoveFile Id=\"RemoveSSH\" Name=\"ssh.exe\" On=\"install\"/>\n      </Component>\n      <Component Id=\"Cmp_WintunDll\" Directory=\"INSTALLDIR\" Guid=\"*\">\n        <File Id=\"WintunDll\" Source=\"wintun.dll\" KeyPath=\"yes\"/>\n      </Component>\n      <Component Id=\"Cmp_TelepresenceExe\" Directory=\"INSTALLDIR\" Guid=\"*\">\n        <File Id=\"TelepresenceExe\" Source=\"telepresence.exe\" Checksum=\"yes\" KeyPath=\"yes\"/>\n      </Component>\n      <Component Id=\"Cmp_PathEnv\" Directory=\"INSTALLDIR\" Guid=\"a1b2c3d4-e5f6-7890-abcd-ef1234567890\">\n        <Environment Id=\"PATH\" Name=\"PATH\" Value=\"[INSTALLDIR]\" Permanent=\"no\" Part=\"last\" Action=\"set\" System=\"yes\"/>\n      </Component>\n    </ComponentGroup>\n\n    <ComponentGroup Id=\"TelepresenceService\">\n      <Component Id=\"Cmp_TelepresenceService\" Guid=\"*\" Directory=\"INSTALLDIR\">\n        <File Id=\"TelepresenceDaemonExe\" Source=\"TelepresenceDaemon.exe\" KeyPath=\"yes\"/>\n        <ServiceInstall Id=\"svc.Install\"\n            Name=\"TelepresenceDaemon\"\n            Type=\"ownProcess\"\n            Start=\"auto\"\n            ErrorControl=\"normal\"\n            Account=\"LocalSystem\"\n            DisplayName=\"Telepresence Daemon\"\n            Interactive=\"no\"\n            Description=\"Provides cluster connectivity\"\n            Arguments='--executable \"[#TelepresenceExe]\" --config \"[CONFIGDIR]config.yml\" --loglevel [LOGLEVEL] --logfile \"[LOGDIR]rootd.log\" --address \":[PORT]\" --pprof [PPROFPORT]'/>\n        <ServiceControl Id=\"svc.Control\"\n            Name=\"TelepresenceDaemon\"\n            Start=\"install\"\n            Stop=\"both\"\n            Remove=\"uninstall\"\n            Wait=\"yes\"/>\n      </Component>\n      <Component Id=\"Cmd_RemoveRootd\" Guid=\"0ddbad7d-65d2-417d-a4af-f2d3492d3cd6\" Directory=\"ROOTD\">\n        <RemoveFile Id=\"PurgeRootdFolder\" Name=\"*.*\" On=\"uninstall\"/>\n        <RemoveFolder On=\"uninstall\"/>\n      </Component>\n      <Component Id=\"Cmd_RemoveCache\" Guid=\"6ae39c09-285a-4e3f-9add-f9aad238cc15\" Directory=\"CACHEDIR\">\n        <RemoveFile Id=\"PurgeCacheFolder\" Name=\"*.*\" On=\"uninstall\"/>\n        <RemoveFolder On=\"uninstall\"/>\n      </Component>\n    </ComponentGroup>\n\n    <Feature Id=\"Main\"\n        Level=\"1\"\n        AllowAbsent=\"no\"\n        AllowAdvertise=\"no\"\n        Title=\"Telepresence CLI\"\n        Description=\"Installs everything needed to interact with services in your cluster.\"\n        Display=\"expand\"\n        ConfigurableDirectory=\"INSTALLDIR\">\n      <ComponentGroupRef Id=\"TelepresenceFiles\"/>\n      <Feature Id=\"Service\"\n          Level=\"2\"\n          AllowAbsent=\"yes\"\n          AllowAdvertise=\"no\"\n          Title=\"Telepresence Network Service\"\n          Description=\"Runs the telepresence root daemon as a Windows service, so that no administrative privileges are needed when using the CLI.\"\n          Display=\"expand\"\n          ConfigurableDirectory=\"INSTALLDIR\">\n        <ComponentGroupRef Id=\"TelepresenceService\"/>\n      </Feature>\n    </Feature>\n\n    <WixVariable Id=\"WixUIDialogBmp\" Value=\"dialog.png\"/>\n    <WixVariable Id=\"WixUIBannerBmp\" Value=\"banner.png\"/>\n\n    <UI>\n      <ui:WixUI Id=\"WixUI_Mondo\" InstallDirectory=\"INSTALLDIR\"/>\n\n      <!-- Daemon servic configuration dialog. Only visible when the service is installed -->\n      <Dialog Id=\"PortDlg\" Width=\"370\" Height=\"270\" Title=\"Telepresence Core Setup\">\n        <!-- Make this dialog look like the other ones (no localization yet) -->\n        <Control Id=\"BannerBitmap\" Type=\"Bitmap\" X=\"0\" Y=\"0\" Width=\"370\" Height=\"44\" TabSkip=\"no\"\n            Text=\"WixUI_Bmp_Banner\"/>\n        <Control Id=\"BannerLine\" Type=\"Line\" X=\"0\" Y=\"44\" Width=\"370\" Height=\"0\"/>\n        <Control Id=\"BottomLine\" Type=\"Line\" X=\"0\" Y=\"234\" Width=\"370\" Height=\"0\"/>\n        <Control Id=\"Title\" Type=\"Text\" X=\"15\" Y=\"6\" Width=\"200\" Height=\"15\" Transparent=\"yes\" NoPrefix=\"yes\"\n            Text=\"{\\WixUI_Font_Title}Daemon Configuration\"/>\n        <Control Id=\"Description\" Type=\"Text\" X=\"25\" Y=\"23\" Width=\"280\" Height=\"15\" Transparent=\"yes\" NoPrefix=\"yes\"\n            Text=\"Configure the Telepresence Service Daemon\"/>\n\n        <!-- Navigation -->\n        <Control Id=\"Back\" Type=\"PushButton\" X=\"180\" Y=\"243\" Width=\"56\" Height=\"17\" Text=\"Back\">\n          <Publish Event=\"NewDialog\" Value=\"CustomizeDlg\" Condition=\"WixUI_InstallMode = &quot;InstallCustom&quot;\"/>\n          <Publish Event=\"NewDialog\" Value=\"SetupTypeDlg\" Condition=\"WixUI_InstallMode = &quot;InstallComplete&quot;\"/>\n        </Control>\n        <Control Id=\"Next\" Type=\"PushButton\" X=\"236\" Y=\"243\" Width=\"56\" Height=\"17\" Default=\"yes\" Text=\"Next\">\n          <Publish Event=\"EndDialog\" Value=\"Return\"/>\n        </Control>\n        <Control Id=\"Cancel\" Type=\"PushButton\" X=\"304\" Y=\"243\" Width=\"56\" Height=\"17\" Cancel=\"yes\" Text=\"Cancel\">\n          <Publish Event=\"SpawnDialog\" Value=\"CancelDlg\"/>\n        </Control>\n\n        <!--Property controls -->\n        <Control Id=\"SvcPortLabel\" Type=\"Text\" X=\"15\" Y=\"80\" Width=\"85\" Height=\"15\" Text=\"Daemon Port number:\"/>\n        <Control Id=\"SvcPortEdit\" Type=\"Edit\" X=\"100\" Y=\"77\" Width=\"35\" Height=\"20\" Property=\"PORT\" Integer=\"yes\"\n            RightAligned=\"yes\"/>\n        <Control Id=\"SvcPortHelp\" Type=\"Text\" X=\"140\" Y=\"80\" Width=\"130\" Height=\"15\"\n            Text=\"0 = select random available port.\"/>\n        <Control Id=\"PProfPortLabel\" Type=\"Text\" X=\"15\" Y=\"110\" Width=\"85\" Height=\"15\" Text=\"PProf Port number:\"/>\n        <Control Id=\"PProfPortEdit\" Type=\"Edit\" X=\"100\" Y=\"107\" Width=\"35\" Height=\"20\" Property=\"PPROFPORT\"\n            Integer=\"yes\"\n            RightAligned=\"yes\"/>\n        <Control Id=\"PProfPortHelp\" Type=\"Text\" X=\"140\" Y=\"110\" Width=\"130\" Height=\"15\"\n            Text=\"0 = don't start pprof server.\"/>\n        <Control Id=\"LogLevelLabel\" Type=\"Text\" X=\"15\" Y=\"140\" Width=\"85\" Height=\"15\" Text=\"Log-Level:\"/>\n        <Control Id=\"LogLevel\" Type=\"ListView\" X=\"100\" Y=\"137\" Width=\"70\" Height=\"70\" Sunken=\"yes\" Property=\"LOGLEVEL\">\n          <ListView Property=\"LOGLEVEL\">\n            <ListItem Value=\"error\" Text=\"Error\"/>\n            <ListItem Value=\"warning\" Text=\"Warning\"/>\n            <ListItem Value=\"info\" Text=\"Info\"/>\n            <ListItem Value=\"debug\" Text=\"Debug\"/>\n          </ListView>\n        </Control>\n      </Dialog>\n\n      <Dialog Id=\"ErrorInvalidPort\" Width=\"260\" Height=\"85\" Title=\"Error\">\n        <Control Id=\"ErrorText\" Type=\"Text\" X=\"48\" Y=\"15\" Width=\"200\" Height=\"30\"\n            Text=\"Ports must be a number from 0 to 65535.\"/>\n        <Control Id=\"OK\" Type=\"PushButton\" X=\"102\" Y=\"60\" Width=\"56\" Height=\"17\" Text=\"OK\">\n          <Publish Event=\"EndDialog\" Value=\"Return\"/>\n        </Control>\n      </Dialog>\n\n      <!-- Jump the LicenseAgreementDlg -->\n      <Publish Dialog=\"WelcomeDlg\" Control=\"Next\" Event=\"NewDialog\" Value=\"SetupTypeDlg\" Order=\"10\"/>\n      <Publish Dialog=\"SetupTypeDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"WelcomeDlg\" Order=\"10\"/>\n\n      <!-- Let SetupTypeDlg jump to PortDlg if the Service feature is enabled -->\n      <Publish Dialog=\"SetupTypeDlg\" Control=\"CompleteButton\" Event=\"NewDialog\" Value=\"PortDlg\" Order=\"10\"/>\n      <Publish Dialog=\"CustomizeDlg\" Control=\"Next\" Event=\"NewDialog\" Value=\"PortDlg\" Order=\"10\"\n          Condition=\"&amp;Service = 3\"/>\n      <Publish Dialog=\"PortDlg\" Control=\"Next\" Event=\"NewDialog\" Value=\"VerifyReadyDlg\" Order=\"10\"\n          Condition=\"PORT &gt;= 0 AND PORT &lt;= 65535 AND PPROFPORT &gt;= 0 AND PPROFPORT &lt;= 65535\"/>\n      <Publish Dialog=\"PortDlg\" Control=\"Next\" Event=\"SpawnDialog\" Value=\"ErrorInvalidPort\" Order=\"10\"\n          Condition=\"NOT (PORT &gt;= 0 AND PORT &lt;= 65535 AND PPROFPORT &gt;= 0 AND PPROFPORT &lt;= 65535)\"/>\n      <Publish Dialog=\"VerifyReadyDlg\" Control=\"Back\" Event=\"NewDialog\" Value=\"PortDlg\" Order=\"10\"\n          Condition=\"&amp;Service = 3\"/>\n    </UI>\n  </Package>\n</Wix>\n"
  },
  {
    "path": "build-aux/wix-installer/Makefile",
    "content": "# Makefile — complete Telepresence Windows installer\n# Run: make bundle          → produces TelepresenceInstall.exe\n#      make clean           → remove everything\n\nSHELL:=$(shell which bash)\n\n.PHONY: FORCE\n\n# VERSION is the telepresence version to bundle. Use TELEPRESENCE_VERSION if set (for CI).\nVERSION          ?= $(if $(TELEPRESENCE_VERSION),$(patsubst v%,%,$(TELEPRESENCE_VERSION)),2.26.0)\nARCH             ?= amd64\nWIX              := wix\nGO               := go\nCURL             := curl\nUNZIP            := unzip\nSEVENZ           := 7z      # install: sudo apt install p7zip-full\n\nWINFSP_VERSION   := 2.1.25156\nSSHFS_VERSION    := 3.7.21011\nWINTUN_VERSION   := 0.14.1\nWINTUN_SUM       := 07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51  # update if version changes\n\nBUILD            := ../../build-output\nBINDIR           := $(BUILD)/bin\nMSIDIR           := $(BUILD)/msi\nZIPDIR           := $(BUILD)/zip\nEXE              := telepresence.exe\nBUNDLE           := TelepresenceInstall.exe\nMAIN_MSI         := MainPackage.msi\nLICENSE          := License.rtf\nDAEMON_EXE       := TelepresenceDaemon.exe\nBUNDLE_VERSION   := $(word 1, $(subst -, ,$(TELEPRESENCE_VERSION:v%=%))).0\nWIXDEFS          := -d BundleVersion=$(BUNDLE_VERSION)\n\n.PHONY: all bundle msi deps clean\n\nall: bundle\n\n# Final single-click installer\nbundle: $(BINDIR)/$(BUNDLE)\n\t@echo \"SUCCESS! Installer ready: $<\"\n\t@du -h $<\n\n$(BINDIR)/$(BUNDLE): Telepresence.wxs $(MSIDIR)/$(MAIN_MSI) $(MSIDIR)/winfsp.msi $(MSIDIR)/sshfs-win.msi | $(BINDIR)\n\t$(WIX) build $(WIXDEFS) \\\n\t\t-ext WixToolset.BootstrapperApplications.wixext \\\n\t\t-ext WixToolset.UI.wixext \\\n\t\t-arch x64 \\\n\t\t-src Telepresence.wxs \\\n\t\t-bindpath . \\\n\t\t-bindpath \"$(MSIDIR)\" \\\n\t\t-o $@\n\n# Main MSI (binary, service daemon, and wintun.dll)\n$(MSIDIR)/$(MAIN_MSI): MainPackage.wxs Dialogs_en-us.wxl $(BINDIR)/$(EXE) $(BINDIR)/$(DAEMON_EXE) $(BINDIR)/wintun.dll $(BUILD)/$(LICENSE) | $(MSIDIR)\n\t$(WIX) build $(WIXDEFS) \\\n\t\t-ext WixToolset.UI.wixext \\\n\t\t-ext WixToolset.Util.wixext \\\n\t\t-arch x64 \\\n\t\t-loc Dialogs_en-us.wxl \\\n\t\t-bindpath . \\\n\t\t-bindpath \"$(BUILD)\" \\\n\t\t-bindpath \"$(BINDIR)\" \\\n\t\t-o $@ $<\n\n$(ZIPDIR):\n\tmkdir -p $@\n\n$(BINDIR):\n\tmkdir -p $@\n\n$(MSIDIR):\n\tmkdir -p $@\n\n$(BUILD):\n\tmkdir -p $@\n\n# Dependencies\ndeps: $(MSIDIR)/winfsp.msi $(MSIDIR)/sshfs-win.msi $(BINDIR)/wintun.dll $(BINDIR)/$(EXE) $(BINDIR)/$(DAEMON_EXE) $(BUILD)/$(LICENSE)\n\nWF_URL := https://github.com/winfsp/winfsp/releases/download/v2.1/winfsp-$(WINFSP_VERSION).msi\n\n$(MSIDIR)/winfsp.msi: | $(MSIDIR)\n\t@if [ -f \"$@\" ]; then \\\n\t\t$(CURL) -L --fail -z \"$@\" -R -o \"$@\" \"$(WF_URL)\"; \\\n\telse \\\n\t\t$(CURL) -L --fail -R -o \"$@\" \"$(WF_URL)\"; \\\n\tfi\n\nSSHFS_URL := https://github.com/winfsp/sshfs-win/releases/download/v$(SSHFS_VERSION)/sshfs-win-$(SSHFS_VERSION)-x64.msi\n\n$(MSIDIR)/sshfs-win.msi: | $(MSIDIR)\n\t@if [ -f \"$@\" ]; then \\\n\t\t$(CURL) -L --fail -z \"$@\" -R -o \"$@\" \"$(SSHFS_URL)\"; \\\n\telse \\\n\t\t$(CURL) -L --fail -R -o \"$@\" \"$(SSHFS_URL)\"; \\\n\tfi\n\n# wintun.dll\n# Paths\nWT_ZIP         := $(ZIPDIR)/wintun-$(WINTUN_VERSION).zip\nWT_URL         := https://www.wintun.net/builds/wintun-$(WINTUN_VERSION).zip\nWT_FILE        := wintun/bin/$(ARCH)/wintun.dll\n\n$(BINDIR)/wintun.dll: FORCE | $(BINDIR) $(ZIPDIR)\n\t@echo \"Checking for newer $@...\"\n\t@if [ -f \"$@\" -a -f \"$(WT_ZIP)\" ]; then \\\n\t\t# Try conditional request first \\\n\t\thttp_code=$$($(CURL) -L --fail -z \"$@\" -R -o \"$(WT_ZIP)\" --write-out '%{http_code}' \"$(WT_URL)\"); \\\n\t\tif [ \"$$http_code\" = \"200\" ]; then \\\n\t\t\techo \"Downloaded updated wintun $(WINTUN_VERSION). Extracting $@...\"; \\\n\t\t\t$(UNZIP) -op \"$(WT_ZIP)\" \"$(WT_FILE)\" > \"$@\" && touch -r \"$(WT_ZIP)\" \"$@\"; \\\n\t\telif [ \"$$http_code\" = \"304\" ]; then \\\n\t\t\techo \"$@ already up to date (not modified)\"; \\\n\t\telse \\\n\t\t\techo \"Download failed (status $$?)\"; \\\n\t\t\texit 1; \\\n\t\tfi \\\n\telse \\\n\t\t# File doesn't exist -> full download \\\n\t\techo \"Downloading wintun $(WINTUN_VERSION) (first time)...\"; \\\n\t\t$(CURL) -L --fail -R -o \"$(WT_ZIP)\" \"$(WT_URL)\" || { \\\n\t\t\trm -f \"$@\"; exit 1; \\\n\t\t}; \\\n\t\techo \"Extracting $@...\"; \\\n\t\t$(UNZIP) -op \"$(WT_ZIP)\" \"$(WT_FILE)\" > \"$@\" && touch -r \"$(WT_ZIP)\" \"$@\"; \\\n\tfi\n\n# Telepresence binary\n# Paths\nTP_ZIP         := $(ZIPDIR)/telepresence-windows-$(ARCH)-v$(VERSION).zip\nTP_URL         := https://github.com/telepresenceio/telepresence/releases/download/v$(VERSION)/telepresence-windows-$(ARCH).zip\n\n# For CI builds, the binary may already exist from the main build process.\n# Check if it exists and is a valid Windows executable (has MZ header).\n$(BINDIR)/$(EXE): FORCE | $(BINDIR) $(ZIPDIR)\n\t@if [ -f \"$@\" ] && head -c2 \"$@\" 2>/dev/null | grep -q \"MZ\"; then \\\n\t\techo \"$@ already exists and is valid (using local build)\"; \\\n\telif [ -f \"$@\" -a -f \"$(TP_ZIP)\" ]; then \\\n\t\techo \"Checking for newer Telepresence v$(VERSION)...\"; \\\n\t\thttp_code=$$($(CURL) -L --fail -z \"$@\" -R -o \"$(TP_ZIP)\" --write-out '%{http_code}' \"$(TP_URL)\"); \\\n\t\tif [ \"$$http_code\" = \"200\" ]; then \\\n\t\t\techo \"Downloaded updated telepresence v$(VERSION). Extracting $@...\"; \\\n\t\t\t$(UNZIP) -o \"$(TP_ZIP)\" $(EXE) -d $(dir $@); \\\n\t\t\ttouch -r \"$(TP_ZIP)\" \"$@\"; \\\n\t\telif [ \"$$http_code\" = \"304\" ]; then \\\n\t\t\techo \"$@ already up to date (not modified)\"; \\\n\t\telse \\\n\t\t\techo \"Download failed (status $$http_code)\"; \\\n\t\t\texit 1; \\\n\t\tfi; \\\n\telse \\\n\t\techo \"Downloading telepresence v$(VERSION) (first time)...\"; \\\n\t\t$(CURL) -L --fail -R -o \"$(TP_ZIP)\" \"$(TP_URL)\" || { \\\n\t\t\trm -f \"$@\"; exit 1; \\\n\t\t}; \\\n\t\techo \"Extracting $@...\"; \\\n\t\t$(UNZIP) -o \"$(TP_ZIP)\" $(EXE) -d $(dir $@); \\\n\t\ttouch -r \"$(TP_ZIP)\" \"$@\"; \\\n\tfi\n\n# Build the TelepresenceDaemon service wrapper\n$(BINDIR)/$(DAEMON_EXE): tpwrapper.go | $(BINDIR)\n\tGOOS=windows GOARCH=$(ARCH) $(GO) build --trimpath -ldflags=\"-s -w\" -o $@ $<\n\n$(BUILD)/$(LICENSE): ../../LICENSE | $(BUILD)\n\tpwsh -File txt2rtf.ps1 \"$<\" \"$@\"\n\n# First-time setup\ninit:\n\t$(GO) mod init telepresence-wix-customaction\n\t$(GO) get golang.org/x/sys@v0.25.0\n\t$(GO) mod tidy\n\nclean:\n\trm -rf $(BUILD) $(BINDIR) $(MSI) $(BUNDLE) *.wixobj *.wixpdb\n"
  },
  {
    "path": "build-aux/wix-installer/Telepresence.wxs",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\" xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\">\n  <Bundle Name=\"Telepresence\"\n      Manufacturer=\"TelepresenceIO\"\n      Version=\"$(var.BundleVersion)\"\n      UpgradeCode=\"b3716fff-1f9c-4a87-8f7a-8fe4721aee2e\">\n\n    <BootstrapperApplication>\n      <bal:WixStandardBootstrapperApplication\n          Theme=\"hyperlinkSidebarLicense\"\n          LogoFile=\"logo.png\"\n          LogoSideFile=\"sidebar.png\"\n          LicenseUrl=\"\"\n          SuppressOptionsUI=\"yes\"\n          ShowVersion=\"yes\"/>\n    </BootstrapperApplication>\n\n    <Chain>\n      <MsiPackage Id=\"WinFsp\"\n          SourceFile=\"winfsp.msi\"\n          Visible=\"yes\"\n          Vital=\"yes\"\n          EnableFeatureSelection=\"no\"\n          bal:DisplayInternalUICondition=\"WixBundleAction = 6\"\n          LogPathVariable=\"WixBundleLog\">\n      </MsiPackage>\n\n      <MsiPackage Id=\"SshfsWin\"\n          SourceFile=\"sshfs-win.msi\"\n          Visible=\"yes\"\n          Vital=\"yes\"\n          EnableFeatureSelection=\"no\"\n          bal:DisplayInternalUICondition=\"WixBundleAction = 6\"\n          LogPathVariable=\"WixBundleLog\">\n      </MsiPackage>\n\n      <MsiPackage Id=\"TelepresenceCore\"\n          SourceFile=\"MainPackage.msi\"\n          Visible=\"yes\"\n          Vital=\"yes\"\n          bal:DisplayInternalUICondition=\"WixBundleAction = 6\"\n          EnableFeatureSelection=\"yes\"\n          LogPathVariable=\"WixBundleLog\">\n      </MsiPackage>\n    </Chain>\n  </Bundle>\n</Wix>\n"
  },
  {
    "path": "build-aux/wix-installer/config.yml",
    "content": ""
  },
  {
    "path": "build-aux/wix-installer/tpwrapper.go",
    "content": "//go:build windows\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\n\t\"golang.org/x/sys/windows/svc\"\n\t\"golang.org/x/sys/windows/svc/debug\"\n)\n\nconst svcName = \"TelepresenceDaemon\"\n\ntype wrapper struct {\n\texecutable string\n\tcacheDir   string\n\tconfigPath string\n\tlogPath    string\n\tlogLevel   string\n\taddress    string\n\tpprofPort  uint\n}\n\nfunc (w *wrapper) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {\n\tconst cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown\n\n\tchanges <- svc.Status{State: svc.StartPending}\n\n\targs := make([]string, 0, 5)\n\targs = append(args, \"rootd\", \"--config\", w.configPath)\n\tif w.cacheDir != \"\" {\n\t\targs = append(args, \"--cache\", w.cacheDir)\n\t}\n\tif w.logPath != \"\" {\n\t\targs = append(args, \"--logfile\", w.logPath)\n\t}\n\tif w.address != \"\" {\n\t\targs = append(args, \"--address\", w.address)\n\t}\n\tif w.pprofPort > 0 {\n\t\targs = append(args, \"--pprof\", strconv.Itoa(int(w.pprofPort)))\n\t}\n\targs = append(args, \"--managed\")\n\tcmd := exec.Command(w.executable, args...)\n\tcmd.Stdout = os.Stdout\n\tcmd.Stderr = os.Stderr\n\n\tif err := cmd.Start(); err != nil {\n\t\tlog.Printf(\"Failed to start %s: %v\", w.executable, err)\n\t\tchanges <- svc.Status{State: svc.Stopped}\n\t\treturn false, 1\n\t}\n\n\t// Success — we are running\n\tlog.Println(\"telepresence.exe started (PID\", cmd.Process.Pid, \")\")\n\tchanges <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}\n\n\t// Wait for stop request or child exit\n\tgo func() {\n\t\t_ = cmd.Wait() // ignore error, we just want to know when it dies\n\t\tchanges <- svc.Status{State: svc.Stopped}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase c := <-r:\n\t\t\tswitch c.Cmd {\n\t\t\tcase svc.Interrogate:\n\t\t\t\tchanges <- c.CurrentStatus\n\t\t\tcase svc.Stop, svc.Shutdown:\n\t\t\t\tlog.Println(\"Stopping telepresence.exe...\")\n\t\t\t\tchanges <- svc.Status{State: svc.StopPending}\n\t\t\t\t// Graceful SIGTERM first\n\t\t\t\tif err := cmd.Process.Kill(); err != nil {\n\t\t\t\t\tlog.Printf(\"Kill failed: %v\", err)\n\t\t\t\t}\n\t\t\t\treturn false, 0\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc main() {\n\tvar logLevel string\n\tisDebug := false\n\tw := &wrapper{}\n\tflag.BoolVar(&isDebug, \"debug\", false, \"run in console, not as a real service\")\n\tflag.StringVar(&w.executable, \"executable\", \"\", `path to the executable (required)`)\n\tflag.StringVar(&w.configPath, \"config\", \"\", `Path to the Telepresence configuration file (required)`)\n\tflag.StringVar(&logLevel, \"loglevel\", \"info\", `one of error, warning, info, debug, or trace`)\n\tflag.StringVar(&w.cacheDir, \"cache\", \"\", `Path to the Telepresence cache directory`)\n\tflag.StringVar(&w.logPath, \"logfile\", \"\", \"path to the log file\")\n\tflag.StringVar(&w.address, \"address\", \":4037\", \"TCP address to listen to\")\n\tflag.UintVar(&w.pprofPort, \"pprof\", uint(0), \"start pprof server on the given port\")\n\tflag.Parse()\n\tif w.configPath == \"\" {\n\t\tflag.Usage()\n\t\tos.Exit(1)\n\t}\n\tvar err error\n\tswitch logLevel {\n\tcase \"error\", \"warning\", \"info\", \"debug\", \"trace\":\n\t\terr = os.WriteFile(w.configPath, []byte(fmt.Sprintf(\"logLevels:\\n  rootDaemon: %s\\n\", logLevel)), 0o644)\n\tdefault:\n\t\terr = fmt.Errorf(\"invalid loglevel: %q\", logLevel)\n\t}\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif isDebug {\n\t\terr = debug.Run(svcName, w)\n\t} else {\n\t\terr = svc.Run(svcName, w)\n\t}\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "build-aux/wix-installer/txt2rtf.ps1",
    "content": "param(\n    [Parameter(Mandatory)][string]$InputFile,\n    [Parameter(Mandatory)][string]$OutputFile\n)\n\nAdd-Type -AssemblyName System.Windows.Forms\n\n$rtb = New-Object System.Windows.Forms.RichTextBox\n$rtb.LoadFile($InputFile, [System.Windows.Forms.RichTextBoxStreamType]::PlainText)\n$rtb.SaveFile($OutputFile, [System.Windows.Forms.RichTextBoxStreamType]::RichText)\n\nWrite-Host \"Converted: $($InputFile) -> $OutputFile\"\n"
  },
  {
    "path": "charts/chart.go",
    "content": "package charts\n\nimport (\n\t\"archive/tar\"\n\t\"compress/gzip\"\n\t\"embed\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/spf13/afero\"\n\t\"helm.sh/helm/v3/pkg/chart\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype DirType int8\n\nconst (\n\tDirTypeTelepresence   DirType = iota\n\tTelepresenceChartName         = \"telepresence-oss\"\n)\n\n//go:embed all:telepresence-oss\nvar TelepresenceFS embed.FS\n\n// filePriority returns the sort-priority of a filename; higher priority files sorts earlier.\nfunc filePriority(chartName, filename string) int {\n\tprio := map[string]int{\n\t\tfmt.Sprintf(\"%s/Chart.yaml)\", chartName):        4,\n\t\tfmt.Sprintf(\"%s/values.yaml)\", chartName):       3,\n\t\tfmt.Sprintf(\"%s/values.schema.json\", chartName): 2,\n\t\t// \"telepresence/templates-oss/**\":    1,\n\t\t// \"otherwise\":                        0,\n\t}[filename]\n\tif prio == 0 && strings.HasPrefix(filename, fmt.Sprintf(\"%s/templates/\", chartName)) {\n\t\tprio = 1\n\t}\n\treturn prio\n}\n\nfunc addFile(tarWriter *tar.Writer, vfs fs.FS, filename string, content []byte) error {\n\tvar header *tar.Header\n\t// Build the tar.Header.\n\tfi, err := fs.Stat(vfs, filename)\n\tif err == nil {\n\t\theader, err = tar.FileInfoHeader(fi, \"\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn err\n\t\t}\n\t\theader = &tar.Header{}\n\t}\n\theader.Name = filename\n\theader.Mode = 0o644\n\theader.Size = int64(len(content))\n\n\t// Write the tar.Header.\n\tif err := tarWriter.WriteHeader(header); err != nil {\n\t\treturn err\n\t}\n\n\t// Write the content.\n\tif _, err := tarWriter.Write(content); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\ntype ChartOverlayFuncDef func(base afero.Fs) (afero.Fs, error)\n\n// ChartOverlayFunc can be used by module extensions to add or overwrite the charts directory.\n// type ChartOverlayFunc func(base afero.Fs) (afero.Fs, error).\nvar ChartOverlayFunc map[DirType]ChartOverlayFuncDef //nolint:gochecknoglobals // extension point\n\n// WriteChart is a minimal `helm package`.\nfunc WriteChart(helmChartDir DirType, out io.Writer, chartName string, version semver.Version, overlays ...fs.FS) error {\n\tembedChart := map[DirType]embed.FS{\n\t\tDirTypeTelepresence: TelepresenceFS,\n\t}[helmChartDir]\n\n\tvar baseDir fs.FS = embedChart\n\tif chartOverlayFunc, ok := ChartOverlayFunc[helmChartDir]; ok {\n\t\tbase := afero.FromIOFS{FS: embedChart}\n\t\tovl, err := chartOverlayFunc(base)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tbaseDir = afero.NewIOFS(afero.NewCopyOnWriteFs(base, ovl))\n\t}\n\n\tvar filenames []string\n\tif err := fs.WalkDir(baseDir, \".\", func(filename string, dirent fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif dirent.Type().IsRegular() {\n\t\t\tfilenames = append(filenames, filename)\n\t\t}\n\t\treturn nil\n\t}); err != nil {\n\t\treturn err\n\t}\n\tsort.Slice(filenames, func(i, j int) bool {\n\t\tiName := filenames[i]\n\t\tjName := filenames[j]\n\n\t\t// higher priority files sorts earlier.\n\t\tiPrio := filePriority(chartName, iName)\n\t\tjPrio := filePriority(chartName, jName)\n\t\tif d := iPrio - jPrio; d != 0 {\n\t\t\treturn d > 0\n\t\t}\n\n\t\t// priority is the same\n\t\treturn iName < jName\n\t})\n\n\tzipper := gzip.NewWriter(out)\n\tzipper.Extra = []byte(\"+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=\") // magic number for Helm\n\tzipper.Comment = \"Helm\"\n\n\ttarWriter := tar.NewWriter(zipper)\n\n\tfor _, filename := range filenames {\n\t\tswitch filename {\n\t\tcase fmt.Sprintf(\"%s/values.schema.yaml\", chartName):\n\t\t\tcontent, err := fs.ReadFile(baseDir, filename)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefs, err := fs.ReadFile(baseDir, fmt.Sprintf(\"%s/k8s-defs.json\", chartName))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontentMap := make(map[string]any)\n\t\t\terr = yaml.Unmarshal(content, &contentMap)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefsMap := make(map[string]any)\n\t\t\terr = json.Unmarshal(defs, &defsMap)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontentMap[\"definitions\"] = defsMap[\"definitions\"]\n\t\t\tcontent, err = json.Marshal(contentMap)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = addFile(tarWriter, baseDir, fmt.Sprintf(\"%s/values.schema.json\", chartName), content); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase fmt.Sprintf(\"%s/Chart.yaml\", chartName):\n\t\t\tcontent, err := fs.ReadFile(baseDir, filename)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvar dat chart.Metadata\n\t\t\tif err := yaml.Unmarshal(content, &dat); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tvs := version.String()\n\t\t\tdat.Version = vs\n\t\t\tdat.AppVersion = vs\n\t\t\tcontent, err = yaml.Marshal(dat)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = addFile(tarWriter, baseDir, filename, content); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase fmt.Sprintf(\"%s/k8s-defs.json\", chartName):\n\t\t\t// Don't include k8s-defs.json to the chart package\n\t\tdefault:\n\t\t\tcontent, err := fs.ReadFile(baseDir, filename)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = addFile(tarWriter, baseDir, filename, content); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif err := tarWriter.Close(); err != nil {\n\t\treturn err\n\t}\n\tif err := zipper.Close(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "charts/telepresence-oss/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*.orig\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n.vscode/\n"
  },
  {
    "path": "charts/telepresence-oss/Chart.yaml",
    "content": "apiVersion: v2\nname: telepresence-oss\ndescription: A chart for deploying the server-side components of Telepresence\ntype: application\nversion: \"1.1.1-bogus.overwritten.by.chart.go\"\nkeywords:\n  - ambassador\n  - telepresence\n  - traffic-manager\nsources:\n  - https://github.com/telepresenceio/telepresence\nicon: https://raw.githubusercontent.com/telepresenceio/telepresence.io/master/src/assets/images/telepresence-edgy.svg\n\n# Note: This is the version of the Traffic Manager that will be installed by\n# this chart. The telepresence CLI will always attempt to update the Traffic\n# Manager if it is not the same version as the CLI so ensure you are keeping\n# these in sync.\nappVersion: \"1.1.1-bogus.overwritten.by.chart.go\"\n\nannotations:\n  artifacthub.io/license: Apache-2.0\n"
  },
  {
    "path": "charts/telepresence-oss/README.md",
    "content": "# Telepresence\n\n[Telepresence](https://telepresence.io/) is a tool\nthat allows for local development of microservices running in a remote\nKubernetes cluster.\n\nThis chart manages the server-side components of Telepresence so that an\noperations team can give limited access to the cluster for developers to work on\ntheir services.\n\n## Install\n\nThe telepresence binary embeds the helm chart, so the easiest way to install is:\n\n```sh\n$ telepresence helm install [--set x=y | --values <values file>]\n```\n\n## Configuration\n\nThe following tables lists the configurable parameters of the Telepresence chart and their default values.\n\n| Parameter                                            | Description                                                                                                                                                         | Default                                                                     |\n|------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------|\n| affinity                                             | Define the `Node` Affinity and Anti-Affinity for the Traffic Manager.                                                                                               | `{}`                                                                        |\n| agent.image.name                                     | The name of the injected agent image                                                                                                                                | `\"\"`                                                                        |\n| agent.image.pullPolicy                               | Pull policy in the webhook for the traffic agent image                                                                                                              | `IfNotPresent`                                                              |\n| agent.image.pullSecrets                              | The `Secret` storing any credentials needed to access the image in a private registry.                                                                              |                                                                             |\n| agent.image.tag                                      | The tag for the injected agent image                                                                                                                                | `\"\"` (Defined in `appVersion` Chart.yaml)                                   |\n| agent.image.registry                                 | The registry for the injected agent image                                                                                                                           | `ghcr.io/telepresenceio`                                                    |\n| agent.initResources                                  | The resources for the injected init container                                                                                                                       |                                                                             |\n| agent.logLevel                                       | The logging level for the traffic-agent                                                                                                                             | defaults to logLevel                                                        |\n| agent.maxIdleTime                                    | Maximum time the agent is idle (no engagements involving the workload) before it is cleaned up by the traffic manager                                               | `0h` (infinite, never cleaned up)                                           |\n| agent.mountPolicies                                  | The policies for the agents. Key is either volume name or path prefix starting with '/'                                                                             | `/tmp`: Local                                                               |\n| agent.resources                                      | The resources for the injected agent container                                                                                                                      |                                                                             |\n| agent.securityContext                                | The security context to use for the injected agent container                                                                                                        | defaults to the securityContext of the first container of the app           |\n| agent.initSecurityContext                            | The security context to use for the injected init container                                                                                                         | `{}`                                                                        |\n| agent.initContainer.enabled                          | Whether to enable/disable injection of the initContainer                                                                                                            | true                                                                        |\n| agentInjector.certificate.accessMethod               | Method used by the agent injector to access the certificate (watch or mount).                                                                                       | `watch`                                                                     |\n| agentInjector.certificate.certmanager.commonName     | The common name of the generated Certmanager certificate.                                                                                                           | `agent-injector`                                                            |\n| agentInjector.certificate.certmanager.duration       | The certificate validity duration. (optional value)                                                                                                                 | `2160h0m0s`                                                                 |\n| agentInjector.certificate.certmanager.issuerRef.kind | The Issuer kind to use to generate the self signed certificate. (Issuer of ClusterIssuer)                                                                           | `Issuer`                                                                    |\n| agentInjector.certificate.certmanager.issuerRef.name | The Issuer name to use to generate the self signed certificate.                                                                                                     | `telepresence`                                                              |\n| agentInjector.certificate.method                     | Method used when generating the certificate used for mutating webhook (helm, supplied, or certmanager).                                                             | `helm`                                                                      |\n| agentInjector.certificate.regenerate                 | Whether the certificate used for the mutating webhook should be regenerated.                                                                                        | `false`                                                                     |\n| agentInjector.enabled                                | Enable/Disable the agent-injector and its webhook.                                                                                                                  | `true`                                                                      |\n| agentInjector.name                                   | Name to use with objects associated with the agent-injector.                                                                                                        | `agent-injector`                                                            |\n| agentInjector.injectPolicy                           | Determines when an agent is injected, possible values are `OnDemand` and `WhenEnabled`                                                                              | `OnDemand`                                                                  |\n| agentInjector.secret.name                            | The name of the secret the agent-injector webhook uses for authorization with the kubernetes api will expose.                                                       | `mutator-webhook-tls`                                                       |\n| agentInjector.service.type                           | Type of service for the agent-injector.                                                                                                                             | `ClusterIP`                                                                 |\n| agentInjector.webhook.admissionReviewVersions:       | List of supported admissionReviewVersions.                                                                                                                          | `[\"v1\"]`                                                                    |\n| agentInjector.webhook.failurePolicy:                 | Action to take on unexpected failure or timeout of webhook.                                                                                                         | `Ignore`                                                                    |\n| agentInjector.webhook.name                           | The name of the agent-injector webhook                                                                                                                              | `agent-injector-webhook`                                                    |\n| ~~agentInjector.webhook.namespaceSelector~~:         | The namespaceSelector used by the agent-injector webhook when the traffic-manager is not namespaced.  Deprecated, use top level `namespaces` or `namespaceSelector` | {}                                                                          |\n| agentInjector.webhook.port:                          | Port for the service that provides the admission webhook                                                                                                            | `8443`                                                                      |\n| agentInjector.webhook.reinvocationPolicy:            | Specify if the webhook may be called again after the initial webhook call. Possible values are `Never` and `IfNeeded`.                                              | `IfNeeded`                                                                  |\n| agentInjector.webhook.servicePath:                   | Path to the service that provides the admission webhook                                                                                                             | `/traffic-agent`                                                            |\n| agentInjector.webhook.sideEffects:                   | Any side effects the admission webhook makes outside of AdmissionReview.                                                                                            | `None`                                                                      |\n| agentInjector.webhook.timeoutSeconds:                | Timeout of the admission webhook                                                                                                                                    | `5`                                                                         |\n| apiPort                                              | The port used by the Traffic Manager gRPC API                                                                                                                       | 8081                                                                        |\n| client.connectionTTL                                 | Deprecated: using grpc.connectionTTL                                                                                                                                | `24h`                                                                       |\n| client.dns.excludeSuffixes                           | Suffixes for which the client DNS resolver will always fail (or fallback in case of the overriding resolver)                                                        | `[\".com\", \".io\", \".net\", \".org\", \".ru\"]`                                    |\n| client.dns.includeSuffixes                           | Suffixes for which the client DNS resolver will always attempt to do a lookup. Includes have higher priority than excludes.                                         | `[]`                                                                        |\n| client.routing.allowConflictingSubnets               | Allow the specified subnets to be routed even if they conflict with other routes on the local machine.                                                              | `[]`                                                                        |\n| client.routing.alsoProxySubnets                      | The virtual network interface of connected clients will also proxy these subnets                                                                                    | `[]`                                                                        |\n| client.routing.neverProxySubnets                     | The virtual network interface of connected clients never proxy these subnets                                                                                        | `[]`                                                                        |\n| clientRbac.create                                    | Create RBAC resources for non-admin users with this release.                                                                                                        | `false`                                                                     |\n| ~~clientRbac.namespaced~~                            | Restrict the users to specific namespaces. Deprecated and no longer used.                                                                                           | `false`                                                                     |\n| clientRbac.namespaces                                | The namespaces to give users access to.                                                                                                                             | Traffic Manager's namespaces (unless dynamic)                               |\n| clientRbac.subjects                                  | The user accounts to tie the created roles to.                                                                                                                      | `{}`                                                                        |\n| grpc.connectionTTL                                   | The time that the traffic-manager will retain a client connection without any sign of life from the workstation                                                     | `24h`                                                                       |\n| grpc.maxReceiveSize                                  | Max size of a gRCP message                                                                                                                                          | `4Mi`                                                                       |\n| hooks.busybox.image                                  | The name of the image to use for busybox.                                                                                                                           | `busybox`                                                                   |\n| hooks.busybox.imagePullSecrets                       | The `Secret` storing any credentials needed to access the image in a private registry.                                                                              | `[]`                                                                        |\n| hooks.busybox.registry                               | The registry to download the image from.                                                                                                                            | `docker.io`                                                                 |\n| hooks.busybox.tag                                    | Override the version of busybox to be installed.                                                                                                                    | `latest`                                                                    |\n| hooks.curl.registry                                  | The repository to download the image from.                                                                                                                          | `docker.io`                                                                 |\n| hooks.curl.image                                     | The name of the image to use for curl.                                                                                                                              | `curlimages/curl`                                                           |\n| hooks.curl.imagePullSecrets                          | The `Secret` storing any credentials needed to access the image in a private registry.                                                                              | `[]`                                                                        |\n| hooks.curl.pullPolicy                                | Pull policy used when pulling the curl image.                                                                                                                       | `IfNotPresent`                                                              |\n| hooks.curl.tag                                       | Override the version of busybox to be installed.                                                                                                                    | `latest`                                                                    |\n| hooks.podSecurityContext                             | The Kubernetes SecurityContext for the chart hooks `Pod`                                                                                                            | `{}`                                                                        |\n| image.registry                                       | The repository to download the image from. Set `TELEPRESENCE_REGISTRY=image.registry` locally if changing this value.                                               | `ghcr.io/telepresenceio`                                                    |\n| hooks.resources                                      | Define resource requests and limits for the chart hooks                                                                                                             | `{}`                                                                        |\n| hooks.securityContext                                | The Kubernetes SecurityContext for the chart hooks `Container`                                                                                                      | securityContext                                                             |\n| image.imagePullSecrets                               | The `Secret` storing any credentials needed to access the image in a private registry.                                                                              | `[]`                                                                        |\n| image.name                                           | The name of the image to use for the traffic-manager                                                                                                                | `tel2`                                                                      |\n| image.pullPolicy                                     | How the `Pod` will attempt to pull the image.                                                                                                                       | `IfNotPresent`                                                              |\n| image.tag                                            | Override the version of the Traffic Manager to be installed.                                                                                                        | `\"\"` (Defined in `appVersion` Chart.yaml)                                   |\n| livenessProbe                                        | Define livenessProbe for the Traffic Manger.                                                                                                                        | `{}`                                                                        |\n| logLevel                                             | Define the logging level of the Traffic Manager                                                                                                                     | `debug`                                                                     |\n| managerRbac.create                                   | Create RBAC resources for traffic-manager with this release.                                                                                                        | `true`                                                                      |\n| ~~managerRbac.namespaced~~                           | Whether the traffic manager should be restricted to specific namespaces. Deprecated and no longer used.                                                             | `false`                                                                     |\n| ~~managerRbac.namespaces~~                           | Which namespaces the traffic manager should be restricted to. Deprecated, use top level `namespaces` or `namespaceSelector`                                         | `[]`                                                                        |\n| maxNamespaceSpecificWatchers                         | Threshold controlling when the traffic-manager switches from using watchers for each managed namespace to using cluster-wide watchers.                              | `10`                                                                        |\n| namespaces                                           | Declares a fixed set of managed namespaces. Mutually exclusive to `namespaceSelector`                                                                               | `[]`                                                                        |\n| namespaceSelector                                    | Declares the managed namespace using `matchLabels` and `matchExpressions`. Mutually exclusive to `namespaces`                                                       | `{}`                                                                        |\n| nodeSelector                                         | Define which `Node`s you want to the Traffic Manager to be deployed to.                                                                                             | `{}`                                                                        |\n| podAnnotations                                       | Annotations for the Traffic Manager `Pod`                                                                                                                           | `{}`                                                                        |\n| podLabels                                            | Labels for the Traffic Manager `Pod`                                                                                                                                | `{}`                                                                        |\n| podCIDRs                                             | Verbatim list of CIDRs that the cluster uses for pods. Only valid together with `podCIDRStrategy: environment`                                                      | `[]`                                                                        |\n| podCIDRStrategy                                      | Define the strategy that the traffic-manager uses to discover what CIDRs the cluster uses for pods                                                                  | `auto`                                                                      |\n| podSecurityContext                                   | The Kubernetes SecurityContext for the `Pod`                                                                                                                        | `{}`                                                                        |\n| priorityClassName                                    | Name of the existing priority class to be used                                                                                                                      | `\"\"`                                                                        |\n| rbac.only                                            | Only create the RBAC resources and omit the traffic-manger.                                                                                                         | `false`                                                                     |\n| readinessProbe                                       | Define readinessProbe for the Traffic Manger.                                                                                                                       | `{}`                                                                        |\n| resources                                            | Define resource requests and limits for the Traffic Manger.                                                                                                         | `{}`                                                                        |\n| schedulerName                                        | Specify a scheduler for Traffic Manager `Pod` and hooks `Pod`.                                                                                                      |                                                                             |\n| securityContext                                      | The Kubernetes SecurityContext for the `Deployment`                                                                                                                 | `{\"readOnlyRootFilesystem\": true, \"runAsNonRoot\": true, \"runAsUser\": 1000}` |\n| service.type                                         | The type of `Service` for the Traffic Manager.                                                                                                                      | `ClusterIP`                                                                 |\n| telepresenceAPI.port                                 | The port on agent's localhost where the Telepresence API server can be found                                                                                        |                                                                             |\n| timeouts.agentArrival                                | The time that the traffic-manager will wait for the traffic-agent to arrive                                                                                         | `30s`                                                                       |\n| tolerations                                          | Define tolerations for the Traffic Manager to ignore `Node` taints.                                                                                                 | `[]`                                                                        |\n| workloads.argoRollouts.enabled                       | Enable/Disable the argo-rollouts integration.                                                                                                                       | `false`                                                                     |\n| workloads.deployments.enabled                        | Enable/Disable the support for Deployments.                                                                                                                         | `true`                                                                      |\n| workloads.replicaSets.enabled                        | Enable/Disable the support for ReplicaSets.                                                                                                                         | `true`                                                                      |\n| workloads.statefulSets.enabled                       | Enable/Disable the support for StatefulSets.                                                                                                                        | `true`                                                                      |\n\n### RBAC\n\nTelepresence requires a cluster for installation but restricted RBAC roles can\nbe used to give users access to create intercepts if they are not cluster\nadmins.\n\nThe chart gives you the ability to create these RBAC roles for your users and\ngive access to the entire cluster or restrict to certain namespaces.\n\nYou can also create a separate release for managing RBAC by setting\n`Values.rbac.only: true`.\n\n### Namespace-scoped traffic manager\n\nTelepresence's Helm chart supports installing a Traffic Manager at the namespace scope.\nYou might want to do this if you have multiple namespaces, say representing multiple different environments, and would like their Traffic Managers to be isolated from one another.\nTo do this, set `managerRbac.namespaced=true` and `managerRbac.namespaces={a,b,c}` to manage namespaces `a`, `b` and `c`.\n\n**NOTE** Do not install namespace-scoped traffic managers and a cluster-scoped traffic manager in the same cluster!\n\n#### Namespace collision detection\n\nThe Telepresence Helm chart will try to prevent namespace-scoped Traffic Managers from managing the same namespaces.\nIt will do this by creating a ConfigMap, called `traffic-manager-claim`, in each namespace that a given install manages.\n\nSo, for example, suppose you install one Traffic Manager to manage namespaces `a` and `b`, as:\n\n```bash\n$ telepresence helm install --namespace a --set 'managerRbac.namespaced=true' --set 'managerRbac.namespaces={a,b}'\n```\n\nYou might then attempt to install another Traffic Manager to manage namespaces `b` and `c`:\n\n```bash\n$ telepresence helm install --namespace c --set 'managerRbac.namespaced=true' --set 'managerRbac.namespaces={b,c}'\n```\n\nThis would fail with an error:\n\n```\nError: rendered manifests contain a resource that already exists. Unable to continue with install: ConfigMap \"traffic-manager-claim\" in namespace \"b\" exists and cannot be imported into the current release: invalid ownership metadata; annotation validation error: key \"meta.helm.sh/release-namespace\" must equal \"c\": current value is \"a\"\n```\n\nTo fix this error, fix the overlap either by removing `b` from the first install, or from the second.\n\n#### Pod CIDRs\n\nThe traffic manager is responsible for keeping track of what CIDRs the cluster uses for the pods. The Telepresence client uses this\ninformation to configure the network so that it provides access to the pods. In some cases, the traffic-manager will not be able to retrieve\nthis information, or will do it in a way that is inefficient. To remedy this, the strategy that the traffic manager uses can be configured\nusing the `podCIDRStrategy`.\n\n| Value          | Meaning                                                                                                                   |\n| -------------- | ------------------------------------------------------------------------------------------------------------------------- |\n| `auto`         | First try `nodePodCIDRs` and if that fails, try `coverPodIPs`                                                             |\n| `coverPodIPs`  | Obtain all IPs from the `podIP` and `podIPs` of all `Pod` resource statuses and calculate the CIDRs needed to cover them. |\n| `environment`  | Pick the CIDRs from the traffic manager's `POD_CIDRS` environment variable. Use `podCIDRs` to set that variable.          |\n| `nodePodCIDRs` | Obtain the CIDRs from the`podCIDR` and `podCIDRs` of all `Node` resource specifications.                                  |\n"
  },
  {
    "path": "charts/telepresence-oss/templates/NOTES.txt",
    "content": "--------------------------------------------------------------------------------\nCongratulations!\n\n\nYou have successfully installed the Traffic Manager component of Telepresence!\nNow your users will be able to `telepresence connect` to this Cluster and create\nintercepts for their services!\n\n--------------------------------------------------------------------------------\nNext Steps\n--------------------------------------------------------------------------------\n\n- Take a look at our RBAC documentation for setting up the minimal required RBAC\nroles for your users at https://www.telepresence.io/docs/reference/rbac\n\n- Ensure that you are keeping up to date with Telepresence releases \nhttps://github.com/telepresenceio/telepresence/releases so that your Traffic\nManager is the same version as the telepresence client your users are running!\n"
  },
  {
    "path": "charts/telepresence-oss/templates/_helpers.tpl",
    "content": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"telepresence.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nTraffic Manager deployment/service name - as of v2.20.3, must be \"traffic-manager\" to align with code base.\n*/}}\n{{- define \"traffic-manager.name\" -}}\n{{- $name := default \"traffic-manager\" }}\n{{- print $name }}\n{{- end -}}\n\n{{- /*\nTraffic Manager Namespace\n*/}}\n{{- define \"traffic-manager.namespace\" -}}\n{{- if .Values.isCI }}\n{{- print \"ambassador\" }}\n{{- else }}\n{{- printf \"%s\" .Release.Namespace }}\n{{- end }}\n{{- end -}}\n\n{{- /*\ntraffic-manager.namespace-list extracts the list of namespace names from the namespaces variable.\nFor backward compatibility, it will also consider names from the deprecated managerRbac.namespaces.\nIt's an error if namespaces and managerRbac.namespaces both have values.\n*/}}\n{{- define \"private.namespace-list\" }}\n  {{- $names := .Values.namespaces }}\n  {{- if .Values.managerRbac.namespaces }}\n    {{- if $names }}\n      {{- fail \"namespaces and managerRbac.namespaces are mutually exclusive\" }}\n    {{- end }}\n    {{- $names = .Values.managerRbac.namespaces }}\n  {{- end }}\n  {{- range $names }}\n    {{- if not (regexMatch `^[a-z0-9]([a-z0-9-]*[a-z0-9])?$` .) }}\n      {{- fail (printf \"namespace %q is not a valid RFC 1123 namespace name\" .) }}\n    {{- end }}\n  {{- else }}\n    {{ $names = list }}\n  {{- end }}\n  {{- toJson (uniq ($names)) }}\n{{- end }}\n\n{{- define \"private.namespaceSelector\" }}\n  {{- $labels := list }}\n  {{- $matches := list }}\n  {{- with .Values.namespaceSelector }}\n    {{- with .matchLabels }}\n      {{- $labels = . }}\n    {{- end }}\n    {{- with .matchExpressions }}\n      {{- $matches = . }}\n    {{- end }}\n  {{- end }}\n  {{- with fromJsonArray (include \"private.namespace-list\" $) }}\n    {{- if (or $labels $matches) }}{{ fail \"namespaces and namespaceSelector are mutually exclusive\" }}{{ end }}\n    {{- $matches = append $matches (dict \"key\" \"kubernetes.io/metadata.name\" \"operator\" \"In\" \"values\" .) }}\n  {{- end }}\n  {{- $selector := dict }}\n  {{- with $labels }}\n    {{- $selector = set $selector \"matchLabels\" . }}\n  {{- end }}\n  {{- with $matches }}\n    {{- $selector = set $selector \"matchExpressions\" . }}\n  {{- end }}\n  {{- toJson $selector }}\n{{- end }}\n\n{{- /*\ntraffic-manager.namespaceSelector extracts the selector to use when selecting namespaces.\n\nThis selector will either include the namespaceSelector variable or include namespaces returned by the\nprivate.namespace-list definition. It will fail if both of them have values.\n\nThe selector will default to the deprecated agentInjector.webhook.namespaceSelector when neither the namespaceSelector\nnor the private.namespace-list definition has any value.\n\nA selector can be dynamic or static. This in turn controls if telepresence is \"cluster-wide\" or \"namespaced\". A dynamic\nselector requires cluster-wide access for the traffic-manager, and only a static selector can serve as base when\ninstalling Role/RoleBinding pairs.\n\nA selector is considered static if it meets the following conditions:\n- The selector must have exactly one element in the `matchLabels` or the `matchExpression`\n  list (if the element is in the `matchLabels` list, it is normalized into \"key in [value]\").\n- The element must meet the following criteria:\n  The `key` of the match expression must be \"kubernetes.io/metadata.name\".\n  The `operator` of the match expression must be \"In\" (case sensitive).\n  The `values` list of the match expression must contain at least one value.\n*/}}\n{{- define \"traffic-manager.namespaceSelector\" }}\n  {{- $selector := mustFromJson (include \"private.namespaceSelector\" $) }}\n  {{- $legacy := false }}\n  {{- if not $selector }}\n    {{- with .Values.agentInjector.webhook.namespaceSelector }}\n      {{- $legacy = true }}\n      {{- $selector = . }}\n    {{- end }}\n  {{- end }}\n  {{- if not (or $legacy (fromJsonArray (include \"traffic-manager.namespaces\" $))) }}\n    {{- /*Ensure that his dynamic selector rejects \"kube-system\" and \"kube-node-lease\" */}}\n    {{- $mes := $selector.matchExpressions }}\n    {{- if not $mes }}\n      {{- $mes = list }}\n    {{- end }}\n    {{- $selector = set $selector \"matchExpressions\" (append $mes\n      (dict \"key\" \"kubernetes.io/metadata.name\" \"operator\" \"NotIn\" \"values\" (list \"kube-system\" \"kube-node-lease\")))\n    }}\n  {{- end }}\n  {{- toJson $selector }}\n{{- end }}\n\n{{- /*\ntraffic-manager.namespaced will yield the string \"true\" if the traffic-manager.namespaceSelector that is static.\n*/}}\n{{- define \"traffic-manager.namespaced\" }}\n  {{- if fromJsonArray (include \"traffic-manager.namespaces\" $) }}\n    {{- true }}\n  {{- end }}\n{{- end }}\n\n{{- /*\ntraffic-manager.namespaces will return a list of namespaces, provided that the traffic-manager.namespaceSelector is static.\n*/}}\n{{- define \"traffic-manager.namespaces\" }}\n  {{- $namespaces := list }}\n  {{- with mustFromJson (include \"private.namespaceSelector\" $) }}\n    {{- if and .matchExpressions (eq (len .matchExpressions) 1) (not .matchLabels) }}\n      {{- with index .matchExpressions 0}}\n        {{- if (and (eq .operator \"In\") (eq .key \"kubernetes.io/metadata.name\")) }}\n          {{- $namespaces = .values }}\n        {{- end }}\n      {{- end }}\n    {{- end }}\n    {{- if and .matchLabels (eq (len .matchLabels) 1) (not .matchExpressions) }}\n      {{- with get .matchLabels \"kubernetes.io/metadata.name\" }}\n        {{- $namespaces = list . }}\n      {{- end }}\n    {{- end }}\n  {{- end }}\n  {{- toJson $namespaces }}\n{{- end }}\n\n{{- /*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"telepresence.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{- /*\nCommon labels\n*/}}\n{{- define \"telepresence.labels\" -}}\n{{ include \"telepresence.selectorLabels\" $ }}\nhelm.sh/chart: {{ include \"telepresence.chart\" $ }}\n{{- if .Chart.AppVersion }}\napp.kubernetes.io/version: {{ .Chart.AppVersion | quote }}\n{{- end }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- /* This value is intentionally undocumented -- it's used by the telepresence binary to determine ownership of the release */}}\n{{- if .Values.createdBy }}\napp.kubernetes.io/created-by: {{ .Values.createdBy }}\n{{- else }}\napp.kubernetes.io/created-by: {{ .Release.Service }}\n{{- end }}\n{{- end }}\n\n{{- /*\nSelector labels\n*/}}\n{{- define \"telepresence.selectorLabels\" -}}\napp: traffic-manager\ntelepresence: manager\n{{- end }}\n\n{{- /*\nClient RBAC name suffix\n*/}}\n{{- define \"telepresence.clientRbacName\" -}}\n{{ printf \"%s-%s\" (include \"telepresence.name\" $) (include \"traffic-manager.namespace\" $) }}\n{{- end -}}\n\n{{- /*\nRBAC rules required to create an intercept in a namespace; excludes any rules that are always cluster wide.\n*/}}\n{{- define \"telepresence.clientRbacInterceptRules\" -}}\n{{- /* Mandatory. Controls namespace access command completion experience */}}\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"get\",\"list\"] {{- /* \"list\" is only necessary if the client should be able to gather the pod logs */}}\n- apiGroups: [\"\"]\n  resources: [\"pods/log\"]\n  verbs: [\"get\"]\n{{- /* All traffic will be routed via the traffic-manager unless a portforward can be created directly to a pod */}}\n- apiGroups: [\"\"]\n  resources: [\"pods/portforward\"]\n  verbs: [\"create\"]\n{{- if and .Values.clientRbac .Values.clientRbac.ruleExtras }}\n{{ template \"clientRbac-ruleExtras\" . }}\n{{- end }}\n{{- end }}\n\n{{/*\nKubernetes version\n*/}}\n{{- define \"kube.version.major\" }}\n{{- $version := regexFind \"^[0-9]+\" .Capabilities.KubeVersion.Major -}}\n{{- printf \"%s\" $version -}}\n{{- end -}}\n\n{{- define \"kube.version.minor\" }}\n{{- $version := regexFind \"^[0-9]+\" .Capabilities.KubeVersion.Minor -}}\n{{- printf \"%s\" $version -}}\n{{- end -}}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/agentInjectorWebhook.yaml",
    "content": "{{- if and (not (and .Values.rbac .Values.rbac.only)) .Values.agentInjector.enabled }}\n{{- $namespaceSelector := mustFromJson (include \"traffic-manager.namespaceSelector\" $) }}\n{{- /*\nPerform a check that the new namespaceSelector doesn't select namespaces that are\nalready managed by some other traffic-manager.\n*/}}\n{{- $namespaces := (lookup \"v1\" \"Namespace\" \"\" \"\").items }}\n{{- $configs := dict }}\n{{- $cmName := include \"traffic-manager.name\" $ }}\n{{- $cmNs := include \"traffic-manager.namespace\" $}}\n{{- /* Find all existing traffic-manager configmaps and their namespaceSelectors */}}\n{{- range $namespaces }}\n  {{- $ns := .metadata.name }}\n  {{- $cm := lookup \"v1\" \"ConfigMap\" $ns $cmName }}\n  {{- with $cm }}\n    {{- with fromYaml (get .data \"namespace-selector.yaml\" ) }}\n      {{- $configs = set $configs $ns . }}\n    {{- end }}\n  {{- end }}\n{{- end }}\n{{- /* No use testing if the added selector is the only one */}}\n{{- if $configs }}\n  {{- $configs = set $configs $cmNs $namespaceSelector }}\n  {{- /* Validate that no selector overlaps with another */}}\n  {{- $allManagedNamespaces := dict }}\n  {{- range $configNs, $config := $configs }}\n    {{- $rqs := $config.matchExpressions }}\n    {{- /* Normalise the selector, i.e. turn each matchLabel into a machRequirement */}}\n    {{- range $key, $value := $config.matchLabels }}\n      {{- $rqs = append $rqs (dict \"key\" $key \"operator\" \"In\" \"values\" (list $value))}}\n    {{- end }}\n    {{- /* Figure out what namespaces this selector selects, and for each one, assert that it's not selected already */}}\n    {{- range $namespaces }}\n      {{- $ns := .metadata.name }}\n      {{- $labels := .metadata.labels }}\n      {{- $isMatch := true }}\n      {{- range $rqs }}\n        {{- $rqMatch := false }}\n        {{- $val := get $labels .key }}\n        {{- if eq .operator \"In\" }}\n          {{- $rqMatch = has $val .values }}\n        {{- else if eq .operator \"NotIn\" }}\n          {{- $rqMatch = not (has $val .values) }}\n        {{- else if eq .operator \"Exists\" }}\n          {{- $rqMatch = not (eq $val \"\") }}\n        {{- else if eq .operator \"DoesNotExist\" }}\n          {{- $rqMatch = eq $val \"\" }}\n        {{- else }}\n          {{- fail printf \"unsupported labelSelectorOperator %s\" .operator}}\n        {{- end }}\n        {{- if not $rqMatch }}\n          {{- $isMatch = false }}\n          {{- break }}\n        {{- end }}\n      {{- end }}\n      {{- if $isMatch }}\n        {{- $conflictingConfig := get $allManagedNamespaces $ns }}\n        {{- if $conflictingConfig }}\n          {{- if eq $conflictingConfig $cmNs }}\n            {{- $conflictingConfig = $configNs }}\n          {{- end }}\n          {{- fail (printf \"traffic-manager in namespace %s already manages namespace %s\" $conflictingConfig $ns) }}\n        {{- end }}\n        {{- $allManagedNamespaces = set $allManagedNamespaces $ns $configNs }}\n      {{- end }}\n    {{- end }}\n  {{- end }}\n{{- end }}\n{{- $altNames := list ( printf \"agent-injector.%s\" (include \"traffic-manager.namespace\" $)) ( printf \"agent-injector.%s.svc\" (include \"traffic-manager.namespace\" $)) -}}\n{{- $genCA := genCA \"agent-injector-ca\" 365 -}}\n{{- $genCert := genSignedCert \"agent-injector\" nil $altNames 365 $genCA -}}\n{{- $secretData := (lookup \"v1\" \"Secret\" (include \"traffic-manager.namespace\" $) .Values.agentInjector.secret.name).data -}}\n{{- $reinvocationPolicy := .Values.agentInjector.webhook.reinvocationPolicy }}\n{{- if (and .Values.agentInjector.mutationAware (not (eq $reinvocationPolicy \"IfNeeded\"))) }}\n   {{- fail (printf \"agentInjector.mutationAware=true cannot be combined with reinvocationPolicy=%s\" $reinvocationPolicy) }}\n{{- end }}\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n{{- if eq .Values.agentInjector.certificate.method \"certmanager\" }}\n  annotations:\n    cert-manager.io/inject-ca-from: {{ include \"traffic-manager.namespace\" $}}/{{ .Values.agentInjector.secret.name }}\n{{- end }}\n  name: {{ .Values.agentInjector.webhook.name }}-{{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nwebhooks:\n{{- with .Values.agentInjector.webhook.admissionReviewVersions }}\n- admissionReviewVersions:\n  {{- toYaml . | nindent 2 }}\n{{- end }}\n  clientConfig:\n{{- if not (eq .Values.agentInjector.certificate.method \"certmanager\") }}\n{{- if and ($secretData) (or (not .Values.agentInjector.certificate.regenerate) (eq .Values.agentInjector.certificate.method \"supplied\") )}}\n    caBundle: {{ or (get $secretData \"ca.crt\") (get $secretData \"ca.pem\") }}\n{{- else }}\n    caBundle: {{ $genCA.Cert | b64enc }}\n{{- end }}\n{{- end }}\n    service:\n      name: {{ .Values.agentInjector.name }}\n      namespace: {{ include \"traffic-manager.namespace\" $ }}\n      path: {{ .Values.agentInjector.webhook.servicePath }}\n      port: {{ .Values.agentInjector.webhook.port }}\n  rules:\n  - apiGroups:\n    - \"\"\n    apiVersions:\n    - v1\n    operations:\n    - CREATE\n    - DELETE\n    resources:\n    - pods\n    scope: '*'\n  failurePolicy: {{ .Values.agentInjector.webhook.failurePolicy }}\n  reinvocationPolicy: {{ $reinvocationPolicy }}\n  name: agent-injector-{{ include \"traffic-manager.namespace\" $ }}.telepresence.io\n  sideEffects: {{ .Values.agentInjector.webhook.sideEffects }}\n  timeoutSeconds: {{ .Values.agentInjector.webhook.timeoutSeconds }}\n  namespaceSelector:\n{{- toYaml $namespaceSelector | nindent 4 }}\n{{- if not (or (eq .Values.agentInjector.certificate.method \"certmanager\") (eq .Values.agentInjector.certificate.method \"supplied\")) }}\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{ .Values.agentInjector.secret.name }}\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\ndata:\n{{- if and ($secretData) (not .Values.agentInjector.certificate.regenerate) }}\n  ca.crt: {{ or (get $secretData \"ca.crt\") (get $secretData \"ca.pem\") }}\n  tls.crt: {{ or (get $secretData \"tls.crt\") (get $secretData \"crt.pem\") }}\n  tls.key: {{ or (get $secretData \"tls.key\") (get $secretData \"key.pem\") }}\n{{- else }}\n  ca.crt: {{ $genCA.Cert | b64enc }}\n  tls.crt: {{ $genCert.Cert | b64enc }}\n  tls.key: {{ $genCert.Key | b64enc }}\n{{- end }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/certificate.yaml",
    "content": "{{- if and (eq .Values.agentInjector.certificate.method \"certmanager\") .Values.agentInjector.enabled }}\napiVersion: cert-manager.io/v1\nkind: Certificate\nmetadata:\n  name: {{ .Values.agentInjector.secret.name }}\nspec:\n  secretName: {{ .Values.agentInjector.secret.name }}\n  dnsNames:\n  - {{ (printf \"%s.%s\" .Values.agentInjector.name .Release.Namespace ) }}\n  - {{ (printf \"%s.%s.svc\" .Values.agentInjector.name .Release.Namespace ) }}\n  {{- with .Values.agentInjector.certificate.certmanager }}\n  {{- toYaml . | nindent 2 }}\n  {{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/clientRbac/cluster-scope.yaml",
    "content": "{{- /*\nThese are the cluster-wide rbac roles + bindings that will be used by users\nwho want to use telepresence once its components have been set\nup in the cluster.\n*/}}\n{{- with .Values.clientRbac }}\n{{- if (and .create (not (or .namespaces (include \"traffic-manager.namespaced\" $)))) }}\n{{- $roleName := include \"telepresence.clientRbacName\" $ }}\n\n---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name:  {{ $roleName }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nrules:\n- apiGroups: [\"\"]\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n{{- include \"telepresence.clientRbacInterceptRules\" $ }}\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ $roleName }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nsubjects:\n{{ toYaml .subjects }}\nroleRef:\n  kind: ClusterRole\n  name: {{ $roleName }}\n  apiGroup: rbac.authorization.k8s.io\n\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/clientRbac/connect.yaml",
    "content": "{{- with .Values.clientRbac }}\n{{- if .create }}\n{{- /*\nClient must have the following RBAC in the traffic-manager.namespace to establish\na port-forward to the traffic-manager pod.\n*/}}\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name:  traffic-manager-connect\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"pods\"]\n    verbs: [\"get\", \"list\"]\n  - apiGroups: [\"\"]\n    resources: [\"services\"]\n    resourceNames:\n      - {{ include \"traffic-manager.name\" $ }}\n    verbs: [\"get\"]\n  - apiGroups: [\"\"]\n    resources: [\"pods/portforward\"]\n    verbs: [\"create\"]\n---\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: traffic-manager-connect\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nsubjects:\n{{ toYaml .subjects }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  name: traffic-manager-connect\n  kind: Role\n\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/clientRbac/namespace-scope.yaml",
    "content": "{{- /*\nThese are the namespace-scoped rbac roles + bindings that will be used by users\nwho want to use telepresence once its components have been set\nup in the cluster.\n*/}}\n{{- with .Values.clientRbac }}\n{{- if .create }}\n{{- $subjects := .subjects }}\n{{- if (not $subjects) }}\n  {{- /* fail comes out really ugly if we just do fail \"the message here\" */}}\n  {{- $msg := \"You must set clientRbac.subjects to a list of valid rbac subjects. See the kubernetes docs for more: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#referring-to-subjects\" }}\n  {{- fail $msg }}\n{{- end }}\n{{- $namespaces := .namespaces }}\n{{- if not $namespaces }}\n  {{ $namespaces = fromJsonArray (include \"traffic-manager.namespaces\" $) }}\n{{- end }}\n{{- $name := include \"telepresence.clientRbacName\" $ }}\n{{- $labels := include \"telepresence.labels\" $ | nindent 4 }}\n{{- range $namespaces }}\n---\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name:  {{ $name }}\n  namespace: {{ . }}\n  labels:\n    {{- $labels }}\nrules:\n{{ include \"telepresence.clientRbacInterceptRules\" $ }}\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: {{ $name }}\n  namespace: {{ . }}\n  labels:\n    {{- $labels }}\nsubjects:\n{{- toYaml $subjects | nindent 0}}\nroleRef:\n  kind: Role\n  name: {{ $name }}\n  apiGroup: rbac.authorization.k8s.io\n\n{{- end }}\n\n{{- $managerNamespace := include \"traffic-manager.namespace\" $ }}\n{{- if and $namespaces (not (has $managerNamespace $namespaces)) }}\n{{- /*\nThis is required only if the client should be permitted to gather the traffic-manager logs, and it\nis only required when the traffic-manager isn't managing its own namespace.\n*/}}\n---\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: traffic-manager-logs\n  namespace: {{ $managerNamespace }}\n  labels:\n    {{- $labels }}\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"pods/log\"]\n    verbs: [\"get\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: traffic-manager-logs\n  namespace: {{ $managerNamespace }}\n  labels:\n    {{- $labels }}\nsubjects:\n{{ toYaml $subjects }}\nroleRef:\n  kind: Role\n  name: traffic-manager-logs\n  apiGroup: rbac.authorization.k8s.io\n\n{{- end }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/deployment.yaml",
    "content": "{{- with .Values }}\n{{- if not (and .rbac .rbac.only) }}\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"traffic-manager.name\" $ }}\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{ include \"telepresence.labels\" $ | nindent 4 }}\nspec:\n  replicas: {{ .replicaCount }}\n  selector:\n    matchLabels:\n      {{ include \"telepresence.selectorLabels\" $ | nindent 6 }}\n  strategy:\n    type: RollingUpdate\n    rollingUpdate:\n      maxSurge: 1        # Allows creating one extra pod during the update\n      maxUnavailable: 0  # Ensures no pods are terminated until new ones are ready\n  template:\n    metadata:\n    {{- with .podAnnotations }}\n      annotations:\n        {{ toYaml . | nindent 8 }}\n    {{- end }}\n      labels:\n        {{ include \"telepresence.selectorLabels\" $ | nindent 8 }}\n    {{- with .podLabels }}\n        {{ toYaml . | nindent 8 }}\n    {{- end }}\n    spec:\n      {{- with .image.imagePullSecrets }}\n      imagePullSecrets:\n        {{ toYaml . | nindent 8 }}\n      {{- end }}\n      securityContext:\n        {{ toYaml .podSecurityContext | nindent 8 }}\n      {{- if .hostNetwork }}\n      hostNetwork: true\n      {{- end }}\n      containers:\n        - name: {{ include \"traffic-manager.name\" $ }}\n          securityContext:\n            {{ toYaml .securityContext | nindent 12 }}\n          {{- with .image }}\n          image: \"{{ .registry }}/{{ .name }}:{{ .tag | default $.Chart.AppVersion }}\"\n          imagePullPolicy: {{ .pullPolicy }}\n          {{- end }}\n          env:\n          - name: LOG_LEVEL\n            value: {{ .logLevel }}\n          {{- with .image }}\n          - name: REGISTRY\n            value: \"{{ .registry }}\"\n          {{- end }}\n          - name: SERVER_PORT\n            value: {{ .apiPort | quote }}\n          - name: POD_CIDR_STRATEGY\n            value: {{ .podCIDRStrategy }}\n          {{- with .podCIDRs }}\n          - name: POD_CIDRS\n            value: \"{{ join \" \" . }}\"\n          {{- end }}\n          {{- if .agentInjector.enabled }}\n          - name: MUTATOR_WEBHOOK_PORT\n            value: {{ .agentInjector.webhook.port | quote }}\n          - name: AGENT_INJECTOR_SECRET\n          {{- if eq .agentInjector.certificate.accessMethod \"mount\" }}\n            value: /var/run/secrets/tls\n          {{- else }}\n            value: {{ .agentInjector.secret.name }}\n          {{- end }}\n          {{- end }}\n          {{- with .telepresenceAPI }}\n          {{- if .port }}\n          - name: AGENT_REST_API_PORT\n            value: {{ .port | quote }}\n          {{- end }}\n          {{- end }}\n          {{- with .grpc }}\n          {{- if .maxReceiveSize }}\n          - name: GRPC_MAX_RECEIVE_SIZE\n            value: {{ .maxReceiveSize }}\n          {{- if and .connectionTTL (not $.Values.client.connectionTTL) }}\n          - name: CLIENT_CONNECTION_TTL\n            value: {{ .connectionTTL }}\n          {{- end }}\n          {{- end }}\n          {{- end }}\n          {{- if .workloads }}\n          {{- with .workloads }}\n          - name: ENABLED_WORKLOAD_KINDS\n            value: >-\n              {{- if or (not .deployments) .deployments.enabled }}\n              Deployment\n              {{- end }}\n              {{- if or (not .statefulSets) .statefulSets.enabled }}\n              StatefulSet\n              {{- end }}\n              {{- if or (not .replicaSets) .replicaSets.enabled }}\n              ReplicaSet\n              {{- end }}\n              {{- if and .argoRollouts .argoRollouts.enabled }}\n              Rollout\n              {{- end }}\n          {{- end }}\n          {{- else }}\n          - name: ENABLED_WORKLOAD_KINDS\n            value: Deployment StatefulSet ReplicaSet\n          {{- end }}\n          {{- with .intercept }}\n          {{- if not (eq .allowGlobalIntercepts nil) }}\n          - name: INTERCEPT_ALLOW_GLOBAL\n            value: {{ .allowGlobalIntercepts | quote }}\n          {{- end }}\n          - name: INTERCEPT_INACTIVE_BLOCK_TIMEOUT\n            value: {{ .inactiveBlockTimeout }}\n          {{- end }}\n      {{- if .agentInjector.enabled }}\n        {{- /*\n        Traffic agent injector configuration\n        */}}\n          - name: AGENT_ARRIVAL_TIMEOUT\n            value: {{ quote (default \"30s\" .timeouts.agentArrival) }}\n          {{- with .agentInjector }}\n          - name: AGENT_INJECT_POLICY\n            value: {{ .injectPolicy }}\n          - name: AGENT_INJECTOR_NAME\n            value:  {{ .name | quote }}\n          - name: AGENT_INJECTOR_MUTATION_AWARE\n            value: {{ (default false .mutationAware) | quote }}\n          {{- end }}\n        {{- /*\n        Traffic agent configuration\n        */}}\n          {{- with .agent }}\n          {{- if .logLevel }}\n          - name: AGENT_LOG_LEVEL\n            value: {{ .logLevel }}\n          {{- end }}\n          {{- if .port }}\n          - name: AGENT_PORT\n            value: {{ .port | quote }}\n          {{- end }}\n          - name: AGENT_ENABLE_H2C_PROBING\n            value: {{ (default false .enableH2cProbing) | quote }}\n          - name: AGENT_CONSUMPTION_METRICS\n            value: {{ (default true .enableConsumptionMetrics) | quote }}\n          {{- if .resources }}\n          - name: AGENT_RESOURCES\n            value: '{{ toJson .resources }}'\n          {{- end }}\n          {{- if .initResources }}\n          - name: AGENT_INIT_RESOURCES\n            value: '{{ toJson .initResources }}'\n          {{- end }}\n          {{- if .mountPolicies }}\n          - name: AGENT_MOUNT_POLICIES\n            value: '{{ toJson .mountPolicies }}'\n          {{- end }}\n          {{- if .maxIdleTime }}\n          - name: AGENT_MAX_IDLE_TIME\n            value: {{ .maxIdleTime }}\n          {{- end }}\n          {{- with .initContainer }}\n          - name: AGENT_INIT_CONTAINER_ENABLED\n            value: {{ .enabled | quote  }}\n          {{- end }}\n          {{- with .image }}\n          {{- if .name }}\n          - name: AGENT_IMAGE_NAME\n            value: {{ .name }}\n          {{- end }}\n          {{- if .tag }}\n          - name: AGENT_IMAGE_TAG\n            value: {{ .tag }}\n          {{- end }}\n          {{- if .registry }}\n          - name: AGENT_REGISTRY\n            value: {{ .registry }}\n          {{- end }}\n          {{- with .pullSecrets }}\n          - name: AGENT_IMAGE_PULL_SECRETS\n            value: '{{ toJson . }}'\n          {{- end }}\n          - name: AGENT_IMAGE_PULL_POLICY\n            value: {{ .pullPolicy }}\n          {{- end }}\n          {{- /* must check against nil. An empty security context is a valid override */}}\n          {{- if not (eq .securityContext nil) }}\n          - name: AGENT_SECURITY_CONTEXT\n            value: '{{ toJson .securityContext }}'\n          {{- end }}\n          {{- /* must check against nil. An empty security context is a valid override */}}\n          {{- if not (eq .initSecurityContext nil) }}\n          - name: AGENT_INIT_SECURITY_CONTEXT\n            value: '{{ toJson .initSecurityContext }}'\n          {{- end }}\n          {{- if .watchRetryInterval }}\n          - name: AGENT_WATCH_RETRY_INTERVAL\n            value: .watchRetryInterval\n          {{- end }}\n          {{- end }}\n          {{- with fromJsonArray (include \"traffic-manager.namespaces\" $) }}\n          {{- /*\n           This environment variable is not used, it is here to force a redeploy of the traffic manager when the list\n           changes, because it updates roles and rolebindings and potentially also changes from roles to clusterroles or\n           vice versa.\n           */}}\n          - name: NOT_USED_NSS\n            value: {{ toJson . | quote }}\n          {{- end }}\n      {{- end }}\n        {{- with .prometheus }}\n          {{- if .port }}  # 0 is false\n          - name: PROMETHEUS_PORT\n            value: \"{{ .port }}\"\n          {{- end }}\n          {{- if .dropClientLabel }}\n          - name: PROMETHEUS_DROP_CLIENT_LABEL\n            value: \"{{ .dropClientLabel }}\"\n          {{- end }}\n        {{- end }}\n          - name: MAX_NAMESPACE_SPECIFIC_WATCHERS\n            value: {{.maxNamespaceSpecificWatchers | quote }}\n          - name: MANAGER_NAMESPACE\n            valueFrom:\n              fieldRef:\n                apiVersion: v1\n                fieldPath: metadata.namespace\n          - name: POD_IP\n            valueFrom:\n              fieldRef:\n                apiVersion: v1\n                fieldPath: status.podIP\n         {{- /*\n        Client configuration\n        */}}\n          {{- with .client }}\n          {{- if .connectionTTL }}\n          - name: CLIENT_CONNECTION_TTL\n            value: {{ .connectionTTL }}\n          {{- end }}\n          {{- with .routing }}\n          {{- if .alsoProxySubnets }}\n          - name: CLIENT_ROUTING_ALSO_PROXY_SUBNETS\n            value: \"{{ join \" \" .alsoProxySubnets }}\"\n          {{- end }}\n          {{- if .neverProxySubnets }}\n          - name: CLIENT_ROUTING_NEVER_PROXY_SUBNETS\n            value: \"{{ join \" \" .neverProxySubnets }}\"\n          {{- end }}\n          {{- if .allowConflictingSubnets }}\n          - name: CLIENT_ROUTING_ALLOW_CONFLICTING_SUBNETS\n            value: \"{{ join \" \" .allowConflictingSubnets }}\"\n          {{- end }}\n          {{- end }}\n          {{- with .dns }}\n          {{- with .excludeSuffixes }}\n          - name: CLIENT_DNS_EXCLUDE_SUFFIXES\n            value: \"{{ join \" \" . }}\"\n          {{- end }}\n          {{- with .includeSuffixes }}\n          - name: CLIENT_DNS_INCLUDE_SUFFIXES\n            value: \"{{ join \" \" . }}\"\n          {{- end }}\n          {{- end }}\n          {{- end }}\n          {{- with .compatibility }}\n          {{- if .version }}\n          - name: COMPATIBILITY_VERSION\n            value: {{ .version }}\n          {{- end }}\n          {{- end }}\n          ports:\n          - name: api\n            containerPort: {{ .apiPort }}\n          - name: https\n            containerPort: {{ .agentInjector.webhook.port }}\n          {{- if .prometheus.port }}  # 0 is false\n          - name: prometheus\n            containerPort: {{ .prometheus.port }}\n          {{- end }}\n          {{- with .livenessProbe }}\n          livenessProbe:\n            {{ toYaml . | nindent 12 }}\n          {{- end }}\n          startupProbe:\n          {{- with .startupProbe }}\n            {{ toYaml . | nindent 12 }}\n          {{- else }}\n            grpc:\n              port: {{ .apiPort }}\n            periodSeconds: 2\n            failureThreshold: 5\n          {{- end }}\n          {{- with .readinessProbe }}\n          readinessProbe:\n            {{ toYaml . | nindent 12 }}\n          {{- end }}\n          {{- with .resources }}\n          resources:\n            {{ toYaml . | nindent 12 }}\n          {{- end }}\n      {{- if eq .agentInjector.certificate.accessMethod \"mount\" }}\n          volumeMounts:\n          {{- if .agentInjector.enabled }}\n            - name: tls\n              mountPath: /var/run/secrets/tls\n              readOnly: true\n          {{- end }}\n      {{- end }}\n      {{- with .schedulerName }}\n      schedulerName: {{ . }}\n      {{- end }}\n      {{- with .nodeSelector }}\n      nodeSelector:\n        {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .affinity }}\n      affinity:\n        {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .tolerations }}\n      tolerations:\n        {{ toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .priorityClassName }}\n      priorityClassName: {{ . | quote }}\n      {{- end }}\n  {{- if eq .agentInjector.certificate.accessMethod \"mount\" }}\n      volumes:\n        {{- if .agentInjector.enabled }}\n        - name: tls\n          secret:\n            defaultMode: 420\n            secretName: {{ .agentInjector.secret.name }}\n        {{- end }}\n  {{- end }}\n      serviceAccountName: traffic-manager\n{{- end }}\n{{- end }}"
  },
  {
    "path": "charts/telepresence-oss/templates/issuer.yaml",
    "content": "{{- if and (eq .Values.agentInjector.certificate.method \"certmanager\") .Values.agentInjector.enabled }}\napiVersion: cert-manager.io/v1\nkind: {{ .Values.agentInjector.certificate.certmanager.issuerRef.kind }}\nmetadata:\n  name: {{ .Values.agentInjector.certificate.certmanager.issuerRef.name }}\nspec:\n  selfSigned: {}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/pre-delete-hook.yaml",
    "content": "{{- if and (not (and .Values.rbac .Values.rbac.only)) .Values.agentInjector.enabled }}\napiVersion: batch/v1\nkind: Job\nmetadata:\n  name: uninstall-agents\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    app.kubernetes.io/managed-by: {{ .Release.Service | quote }}\n    app.kubernetes.io/instance: {{ .Release.Name | quote }}\n    app.kubernetes.io/version: {{ .Chart.AppVersion }}\n    helm.sh/chart: \"{{ .Chart.Name }}-{{ .Chart.Version }}\"\n  annotations:\n    {{- /* This is what defines this resource as a hook. Without this line, the job is considered part of the release. */}}\n    \"helm.sh/hook\": pre-delete\n    \"helm.sh/hook-weight\": \"-5\"\n    \"helm.sh/hook-delete-policy\": before-hook-creation,hook-succeeded\nspec:\n  backoffLimit: 1\n  template:\n    metadata:\n      name: uninstall-agents\n      labels:\n        app.kubernetes.io/managed-by: {{ .Release.Service | quote }}\n        app.kubernetes.io/instance: {{ .Release.Name | quote }}\n        helm.sh/chart: \"{{ .Chart.Name }}-{{ .Chart.Version }}\"\n    spec:\n      securityContext:\n        {{- toYaml .Values.hooks.podSecurityContext | nindent 8 }}\n      restartPolicy: Never\n      {{- with .Values.hooks.curl.imagePullSecrets }}\n      imagePullSecrets:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      containers:\n        - name: uninstall-agents\n          securityContext:\n            {{- if .Values.hooks.securityContext }}\n            {{- toYaml .Values.hooks.securityContext | nindent 12 }}\n            {{- else }}\n            {{- toYaml .Values.securityContext | nindent 12 }}\n            {{- end }}\n          image: \"{{ .Values.hooks.curl.registry }}/{{ .Values.hooks.curl.image }}:{{ .Values.hooks.curl.tag }}\"\n          imagePullPolicy: {{ .Values.hooks.curl.pullPolicy }}\n          volumeMounts:\n            - name: secret-volume\n              mountPath: /secret\n          env:\n            - name: CURL_CA_BUNDLE\n              value: /secret/ca.crt\n          resources:\n            {{- toYaml .Values.hooks.resources | nindent 12 }}\n          command:\n            - sh\n            - -c\n          args:\n            - 'curl --fail --connect-timeout 5 --max-time 60 --request DELETE https://{{ .Values.agentInjector.name }}.{{ include \"traffic-manager.namespace\" $ }}:{{ .Values.agentInjector.webhook.port }}/uninstall || exit 0'\n      volumes:\n        - name: secret-volume\n          secret:\n            secretName: {{ .Values.agentInjector.secret.name }}\n      {{- with .Values.schedulerName }}\n      schedulerName: {{ . }}\n      {{- end }}\n      {{- with .Values.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/routecontroller-daemonset.yaml",
    "content": "{{- $localCluster := or (eq .Values.image.registry \"local\") (hasPrefix \"localhost:\" .Values.image.registry) }}\n{{- $rcEnabled := ternary $localCluster .Values.routeController.enabled (kindIs \"invalid\" .Values.routeController.enabled) }}\n{{- if and .Values.routeController $rcEnabled }}\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: route-controller\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{ include \"telepresence.labels\" $ | nindent 4 }}\nspec:\n  selector:\n    matchLabels:\n      app: route-controller\n  template:\n    metadata:\n      labels:\n        app: route-controller\n    spec:\n      hostNetwork: true\n      dnsPolicy: ClusterFirstWithHostNet\n      serviceAccountName: route-controller\n      containers:\n        - name: route-controller\n          image: \"{{ coalesce .Values.routeController.image.registry .Values.image.registry }}/{{ .Values.routeController.image.name }}:{{ .Chart.AppVersion }}\"\n          imagePullPolicy: {{ coalesce .Values.routeController.image.pullPolicy .Values.image.pullPolicy }}\n          env:\n            - name: LOG_LEVEL\n              value: {{ coalesce .Values.routeController.logLevel .Values.logLevel }}\n            {{- with .Values.routeController.serviceCIDRs }}\n            - name: SERVICE_CIDRS\n              value: {{ join \",\" . }}\n            {{- end }}\n          securityContext:\n            capabilities:\n              add: [\"NET_ADMIN\"]\n          resources:\n            requests:\n              cpu: 10m\n              memory: 32Mi\n            limits:\n              memory: 64Mi\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/routecontroller-rbac.yaml",
    "content": "{{- $localCluster := or (eq .Values.image.registry \"local\") (hasPrefix \"localhost:\" .Values.image.registry) }}\n{{- $rcEnabled := ternary $localCluster .Values.routeController.enabled (kindIs \"invalid\" .Values.routeController.enabled) }}\n{{- if and .Values.routeController $rcEnabled }}\n{{- $roleName := printf \"route-controller-%s\" (include \"traffic-manager.namespace\" $) }}\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: route-controller\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ $roleName }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nrules:\n  - apiGroups: [\"networking.k8s.io\"]\n    resources: [\"servicecidrs\"]\n    verbs: [\"get\", \"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ $roleName }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ $roleName }}\nsubjects:\n  - kind: ServiceAccount\n    name: route-controller\n    namespace: {{ include \"traffic-manager.namespace\" $ }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/service.yaml",
    "content": "{{- with .Values }}\n{{- if not (and .rbac .rbac.only) }}\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"traffic-manager.name\" $ }}\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nspec:\n  type: {{ .service.type }}\n  clusterIP: None\n  ports:\n  - name: api\n    port: {{ .apiPort }}\n    targetPort: api\n  selector:\n    {{- include \"telepresence.selectorLabels\" $ | nindent 4 }}\n{{- if .agentInjector.enabled }}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .agentInjector.name }}\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nspec:\n  type: {{ .service.type }}\n  ports:\n  - name: https\n    port: {{ .agentInjector.webhook.port }}\n    targetPort: https\n  selector:\n    {{- include \"telepresence.selectorLabels\" $ | nindent 4 }}\n{{- end }}\n{{- if .prometheus.port }} # 0 is false\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: telepresence-prometheus\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    name: telepresence-prometheus\nspec:\n  type: {{ .service.type }}\n  ports:\n  - name: telepresence-prometheus\n    port: 80\n    targetPort: prometheus\n  selector:\n    {{- include \"telepresence.selectorLabels\" $ | nindent 4 }}\n{{- end }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/tests/test-connection.yaml",
    "content": "{{- if not (and .Values.rbac .Values.rbac.only) }}\napiVersion: v1\nkind: Pod\nmetadata:\n  name: \"{{ include \"traffic-manager.name\" $ }}-test-connection\"\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\n  annotations:\n    \"helm.sh/hook\": test-success\nspec:\n  {{- with .Values.hooks.busybox.imagePullSecrets }}\n  imagePullSecrets:\n    {{- toYaml . | nindent 2 }}\n  {{- end }}\n  containers:\n    - name: wget\n      image: \"{{ .Values.hooks.busybox.registry }}/{{ .Values.hooks.busybox.image }}:{{ .Values.hooks.busybox.tag }}\"\n      command: ['wget']\n      args: ['{{ include \"traffic-manager.name\" $ }}:8081']\n  restartPolicy: Never\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/trafficManager-configmap.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{ include \"traffic-manager.name\" $ }}\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\ndata:\n{{- if .Values.client }}\n  client.yaml: |\n    {{- toYaml .Values.client | nindent 4 }}\n{{- end }}\n{{- with .Values.intercept }}\n{{- if .environment }}\n  agent-env.yaml: |\n    {{- toYaml .environment | nindent 4 }}\n{{- end }}\n{{- end }}\n  namespace-selector.yaml: |\n    {{- toYaml (mustFromJson (include \"traffic-manager.namespaceSelector\" $)) | nindent 4 }}\n  agent-state.yaml: |\n    AgentStates: {}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/trafficManagerRbac/cluster-scope.yaml",
    "content": "{{- with .Values }}\n{{- if and .managerRbac.create (not (include \"traffic-manager.namespaced\" $)) }}\n{{- /*\nThis file contains all cluster-scoped permissions that the traffic manager needs.\nThis will be larger if namespaced: false, or smaller if it is true\nThis will also likely expand over time as we move more things from the clients\ndomain into the traffic-manager.  But the good news there is that it will\nrequire less permissions in clientRbac.yaml\n*/}}\n{{- $roleName := (printf \"traffic-manager-%s\" (include \"traffic-manager.namespace\" $)) }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: {{ $roleName }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - services\n  verbs:\n  - update {{/* Only needed for upgrade of older versions */}}\n- apiGroups:\n  - \"\"\n  resources:\n  - nodes\n  - services\n  - namespaces\n  - pods\n  verbs:\n  - list\n  - get\n  - watch\n{{- if .agentInjector.enabled }}\n- apiGroups:\n  - \"\"\n  resources:\n  - pods/eviction\n  verbs:\n  - create\n{{- end }}\n- apiGroups:\n  - \"\"\n  resources:\n  - pods/log\n  verbs:\n  - get\n- apiGroups:\n  - \"apps\"\n  resources:\n  - deployments\n  - replicasets\n  - statefulsets\n  verbs:\n  - get\n  - list\n  - watch\n{{- if .agentInjector.enabled }}\n  - patch\n{{- end }}\n{{- if .workloads.argoRollouts.enabled }}\n- apiGroups:\n  - \"argoproj.io\"\n  resources:\n  - rollouts\n  verbs:\n  - get\n  - list\n  - watch\n{{- if .agentInjector.enabled }}\n  - patch\n{{- end }}\n{{- end }}\n- apiGroups:\n    - \"events.k8s.io\"\n  resources:\n    - events\n  verbs:\n    - get\n    - watch\n- apiGroups:\n    - \"networking.k8s.io\"\n  resources:\n    - servicecidrs\n  verbs:\n    - list\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: {{ $roleName }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ $roleName }}\nsubjects:\n- kind: ServiceAccount\n  name: traffic-manager\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/trafficManagerRbac/namespace-scope.yaml",
    "content": "{{- if .Values.managerRbac.create }}\n{{- /*\nThis file contains the various namespace-scoped roles + bindings that the traffic-manager needs.\nThis will likely expand over time as we move more things from the clients\ndomain into the traffic-manager.  But the good news there is that it will\nrequire less permissions in clientRbac.yaml\n*/}}\n{{- $managerNamespace := include \"traffic-manager.namespace\" $}}\n{{- $namespaces := fromJsonArray (include \"traffic-manager.namespaces\" $)}}\n{{- if $namespaces }}\n{{- $interceptEnabled := .Values.agentInjector.enabled}}\n{{- $argoRolloutsEnabled := .Values.workloads.argoRollouts.enabled}}\n{{- $allNamespaces := uniq (append $namespaces $managerNamespace)}}\n\n{{- range $allNamespaces }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: traffic-manager\n  namespace: {{ . }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - services\n  verbs:\n  - update {{/* Only needed for upgrade of older versions */}}\n- apiGroups:\n  - \"\"\n  resources:\n  - services\n  - pods\n  verbs:\n  - list\n  - get\n  - watch\n{{- if $interceptEnabled }}\n- apiGroups:\n  - \"\"\n  resources:\n  - pods/eviction\n  verbs:\n  - create\n{{- end }}\n- apiGroups:\n  - \"\"\n  resources:\n  - pods/log\n  verbs:\n  - get\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - list\n  - get\n  - watch\n  resourceNames:\n{{- if eq . $managerNamespace }}\n  - {{ include \"traffic-manager.name\" $ }}\n{{- end }}\n- apiGroups:\n  - \"apps\"\n  resources:\n  - deployments\n  - replicasets\n  - statefulsets\n  verbs:\n  - get\n  - list\n  - watch\n{{- if $interceptEnabled }}\n  - patch\n{{- end }}\n{{- if $argoRolloutsEnabled }}\n- apiGroups:\n  - \"argoproj.io\"\n  resources:\n  - rollouts\n  verbs:\n  - get\n  - list\n  - watch\n{{- if $interceptEnabled }}\n  - patch\n{{- end }}\n{{- end }}\n- apiGroups:\n    - \"events.k8s.io\"\n  resources:\n    - events\n  verbs:\n    - get\n    - watch\n{{- if eq . $managerNamespace }}\n{{- /* Must be able to get the manager namespace in order to get the install-id */}}\n- apiGroups:\n  - \"\"\n  resources:\n  - namespaces\n  resourceNames:\n  - {{ . }}\n  verbs:\n  - get\n{{- if and (eq (int $.Capabilities.KubeVersion.Major) 1) (lt (int $.Capabilities.KubeVersion.Minor) 33) }}\n{{- /*\nMust be able to make an unsuccessful attempt to create a dummy service in order to receive\nthe error message containing correct service CIDR\n*/}}\n- apiGroups:\n    - \"\"\n  resources:\n    - services\n  verbs:\n    - create\n{{- end }}\n{{- end }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: traffic-manager\n  namespace: {{ . }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: traffic-manager\nsubjects:\n- kind: ServiceAccount\n  name: traffic-manager\n  namespace: {{ $managerNamespace }}\n{{- end }}\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: traffic-manager-cluster-wide-{{ $managerNamespace }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nrules:\n  - apiGroups:\n      - \"networking.k8s.io\"\n    resources:\n      - servicecidrs\n    verbs:\n      - list\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: traffic-manager-cluster-wide-{{ $managerNamespace }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: traffic-manager-cluster-wide-{{ $managerNamespace }}\nsubjects:\n  - kind: ServiceAccount\n    name: traffic-manager\n    namespace: {{ $managerNamespace }}\n{{- else }}\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  namespace: {{ $managerNamespace }}\n  name: traffic-manager\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - services\n  verbs:\n  - create\n- apiGroups:\n    - \"\"\n  resources:\n    - configmaps\n  verbs:\n    - get\n    - list\n    - watch\n    - patch\n    - update\n  resourceNames:\n    - {{ include \"traffic-manager.name\" $ }}\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: traffic-manager\n  namespace: {{ $managerNamespace }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: traffic-manager\nsubjects:\n- kind: ServiceAccount\n  name: traffic-manager\n  namespace: {{ $managerNamespace }}\n{{- end }}\n\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/trafficManagerRbac/service-account.yaml",
    "content": "{{- if .Values.managerRbac.create }}\n{{- /* This file contains the serviceAccount used for the traffic-manager deployment. */}}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: traffic-manager\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels:\n    {{- include \"telepresence.labels\" $ | nindent 4 }}\n\n{{- end }}\n"
  },
  {
    "path": "charts/telepresence-oss/templates/trafficManagerRbac/webhook-secret.yaml",
    "content": "{{- if and (not (eq .Values.agentInjector.certificate.accessMethod \"mount\")) .Values.agentInjector.enabled }}\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  name: agent-injector-webhook-secret\n  labels: {{- include \"telepresence.labels\" $ | nindent 4 }}\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - secrets\n  resourceNames: [ {{ .Values.agentInjector.secret.name }} ]\n  verbs:\n  - get\n  - list\n  - watch\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: agent-injector-webhook-secret\n  namespace: {{ include \"traffic-manager.namespace\" $ }}\n  labels: {{- include \"telepresence.labels\" $ | nindent 4 }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: agent-injector-webhook-secret\nsubjects:\n  - kind: ServiceAccount\n    name: traffic-manager\n    namespace: {{ include \"traffic-manager.namespace\" $ }}\n{{- end }}"
  },
  {
    "path": "charts/telepresence-oss/values.schema.yaml",
    "content": "$schema: https://json-schema.org/draft/2020-12/schema#\n$id: https://github.com/telepresenceio/telepresence-oss.schema.json\ntitle: Telepresence Values\ndescription: Values Schema for the Telepresence OSS Helm Chart\ntype: object\nadditionalProperties: false\nproperties:\n  affinity:\n    description: If specified, the pod's scheduling constraints\n    $ref: \"#/$defs/affinity\"\n\n  agent:\n    description: Telepresence traffic-agent configuration\n    type: object\n    additionalProperties: false\n    properties:\n      enableConsumptionMetrics:\n        description: Enable (the default) or disable the consumption metrics of the traffic-agent. Only valid when the prometheus.port is set.\n        type: boolean\n      enableH2cProbing:\n        description: If enabled, the agent will probe for HTTP2 using clear-text (h2c) support non-TLS ports unless they are configured with appProtocol.\n        type: boolean\n      initContainer:\n        description: Configuration for the init-container that runs before the traffic-agent\n        type: object\n        additionalProperties: false\n        properties:\n          enabled:\n            description: Enable/Disable the init-container\n            type: boolean\n      image:\n        type: object\n        additionalProperties: false\n        properties:\n          name:\n            description: The name of the injected agent image\n            type: string\n          pullPolicy:\n            description: Pull policy in the webhook for the traffic agent image\n            $ref: \"#/$defs/pullPolicy\"\n          pullSecrets:\n            description: The Secret storing any credentials needed to access the image in a private registry\n            type: array\n            items:\n              $ref: \"#/$defs/localObjectReference\"\n          registry:\n            description: The registry for the injected agent image\n            type: string\n          tag:\n            description: Overrides the image tag whose default is the chart appVersion\n            type: string\n      initResources:\n        description: Resource requirements for the init-container\n        $ref: \"#/$defs/resourceRequirements\"\n      initSecurityContext:\n        description: Security context for the init-container\n        $ref: \"#/$defs/securityContext\"\n      logLevel:\n        description: Loglevel for the agent, if different from the traffic-manager\n        $ref: \"#/$defs/logLevel\"\n      maxIdleTime:\n        description: maximum time the agent is idle (no engagements to the workload) before it is cleaned up by the traffic manager\n        $ref: \"#/$defs/duration\"\n      mountPolicies:\n        description: 'List of volume mount policies. Defaults to {\"/tmp\" : \"Ignore\"}'\n        type: object\n        additionalProperties:\n          description: The name or path prefix match of the volume that the policy applies to\n          type: string\n          enum:\n            - Remote\n            - RemoteReadOnly\n            - Local\n            - Ignore\n      port:\n        description: Port number of the first port that the agent will use\n        type: integer\n      resources:\n        description: Resource requirements for the agent\n        $ref: \"#/$defs/resourceRequirements\"\n      securityContext:\n        description: Security context for the agent\n        $ref: \"#/$defs/securityContext\"\n      watchRetryInterval:\n        description: The interval between retries that a watcher uses when the gRPC connection to the traffic manager is lost\n        $ref: \"#/$defs/duration\"\n\n  agentInjector:\n    description: Properties for the agent injector service\n    type: object\n    additionalProperties: false\n    properties:\n      certificate:\n        type: object\n        additionalProperties: false\n        properties:\n          accessMethod:\n            description: Method used by the agent injector to access the certificate (watch or mount)\n            type: string\n            enum:\n              - watch\n              - mount\n          certmanager:\n            type: object\n            additionalProperties: false\n            properties:\n              commonName:\n                description: The common name of the generated Certmanager certificate\n                type: string\n              duration:\n                description: The certificate validity duration\n                $ref: \"#/$defs/duration\"\n              issuerRef:\n                type: object\n                additionalProperties: false\n                properties:\n                  kind:\n                    description: The Issuer kind to use to generate the self signed certificate (Issuer of ClusterIssuer)\n                    type: string\n                    enum:\n                      - Issuer\n                      - ClusterIssuer\n                  name:\n                    description: The Issuer name to use to generate the self signed certificate\n                    type: string\n          method:\n            description: Method used when generating the certificate used for mutating webhook (helm, supplied, or certmanager)\n            type: string\n            enum:\n              - helm\n              - supplied\n              - certmanager\n          regenerate:\n            description: Whether the certificate used for the mutating webhook should be regenerated\n            type: boolean\n      enabled:\n        description: Enable/Disable the agent-injector and its webhook\n        type: boolean\n      injectPolicy:\n        description: Determines when an agent is injected, possible values are OnDemand and WhenEnabled\n        type: string\n        enum:\n          - OnDemand\n          - WhenEnabled\n      mutationAware:\n        description: Include changes to the pod template that are contributed by other injectors. Implies reinvocationPolicy=IfNeeded\n        type: boolean\n      name:\n        description: Name to use with objects associated with the agent-injector\n        type: string\n      secret:\n        type: object\n        properties:\n          name:\n            description: The name of the secret the agent-injector webhook uses for authorization with the kubernetes api will expose\n            $ref: \"#/$defs/rfc1123Label\"\n      service:\n        type: object\n        additionalProperties: false\n        properties:\n          type:\n            description: Type of service for the agent-injector.\n            type: string\n      webhook:\n        type: object\n        additionalProperties: false\n        properties:\n          admissionReviewVersions:\n            description: List of supported admissionReviewVersions\n            type: array\n            items:\n              type: string\n          failurePolicy:\n            description: Action to take on unexpected failure or timeout of webhook.\n            type: string\n            enum:\n              - Fail\n              - Ignore\n          name:\n            description: The name of the agent-injector webhook\n            type: string\n          port:\n            description: Port for the service that provides the admission webhook\n            type: integer\n          reinvocationPolicy:\n            description: Specify if the webhook may be called again after the initial webhook call. Possible values are Never and IfNeeded\n            type: string\n            enum:\n              - IfNeeded\n              - Never\n          servicePath:\n            description: Path to the service that provides the admission webhook\n            type: string\n          sideEffects:\n            description: Any side effects the admission webhook makes outside of AdmissionReview\n            type: string\n            enum:\n              - None\n              - NoneOnDryRun\n              - Some\n              - Unknown\n          timeoutSeconds:\n            description: Timeout of the admission webhook\n            type: integer\n\n  apiPort:\n    description: The port used by the Traffic Manager gRPC API\n    type: integer\n\n  client:\n    type: object\n    properties:\n      connectionTTL:\n        description: Deprecated use grpc.connectionTTL\n        type: string\n        $ref: \"#/$defs/duration\"\n      dns:\n        type: object\n        properties:\n          excludeSuffixes:\n            description: >-\n              Suffixes for which the client DNS resolver will always fail (or fallback in case of the overriding resolver)\n              Defaults to [\".com\", \".io\", \".net\", \".org\", \".ru\"]\n            type: array\n            items:\n              type: string\n          includeSuffixes:\n            description: >-\n              Suffixes for which the client DNS resolver will always attempt to do a lookup. Includes have higher priority\n              than excludes\n            type: array\n            items:\n              type: string\n      routing:\n        type: object\n        properties:\n          allowConflictingSubnets:\n            description: Allow the specified subnets to be routed even if they conflict with other routes on the local machine\n            type: array\n            items:\n              type: string\n          alsoProxySubnets:\n            description: The virtual network interface of connected clients will also proxy these subnets\n            type: array\n            items:\n              type: string\n          neverProxySubnets:\n            description: The virtual network interface of connected clients never proxy these subnets\n            type: array\n            items:\n              type: string\n\n  clientRbac:\n    type: object\n    additionalProperties: false\n    properties:\n      create:\n        description: Create RBAC resources for non-admin users with this release\n        type: boolean\n      namespaces:\n        description: The namespaces to give users access to. Defaults to Traffic Manager's namespaces (unless dynamic)\n        type: array\n        items:\n          $ref: \"#/$defs/rfc1123Label\"\n      ruleExtras:\n        description: If set, run the clientRbac-ruleExtras template to add additional RBAC rules.\n        type: boolean\n      subjects:\n        description: The user accounts to tie the created roles to\n        type: array\n        items:\n          $ref: \"#/$defs/subject\"\n\n  global:\n    type: object\n    additionalProperties: true\n\n  grpc:\n    type: object\n    additionalProperties: false\n    properties:\n      connectionTTL:\n        description: The time that the traffic-manager or traffic-agent will retain a client connection without any sign of life from the workstation\n        type: string\n        $ref: \"#/$defs/duration\"\n      maxReceiveSize:\n        description: maxReceiveSize is a quantity that configures the maximum message size that the traffic manager will service.\n        $ref: \"#/$defs/quantity\"\n\n  hooks:\n    type: object\n    additionalProperties: false\n    properties:\n      busybox:\n        type: object\n        additionalProperties: false\n        properties:\n          image:\n            description: The name of the image to use for busybox\n            type: string\n          imagePullSecrets:\n            description: The Secret storing any credentials needed to access the image in a private registry\n            type: array\n            items:\n              $ref: \"#/$defs/localObjectReference\"\n          pullPolicy:\n            description: Pull policy in the webhook for the image\n            $ref: \"#/$defs/pullPolicy\"\n          registry:\n            description: The registry to download the image from\n            type: string\n          tag:\n            description: Override the version of busybox to be installed\n            type: string\n      curl:\n        type: object\n        additionalProperties: false\n        properties:\n          image:\n            description: The name of the image to use for curl\n            type: string\n          imagePullSecrets:\n            description: The Secret storing any credentials needed to access the image in a private registry\n            type: array\n            items:\n              $ref: \"#/$defs/localObjectReference\"\n          pullPolicy:\n            description: Pull policy in the webhook for the image\n            $ref: \"#/$defs/pullPolicy\"\n          registry:\n            description: The registry to download the image from\n            type: string\n          tag:\n            description: Override the version of curl to be installed\n            type: string\n      podSecurityContext:\n        description: The Kubernetes SecurityContext for the chart hooks Pod\n        $ref: \"#/$defs/podSecurityContext\"\n      resources:\n        description: Define resource requests and limits for the chart hooks\n        $ref: \"#/$defs/resourceRequirements\"\n      securityContext:\n        description: The Kubernetes SecurityContext for the chart hooks Container\n        $ref: \"#/$defs/securityContext\"\n\n  hostNetwork:\n    description: >-\n      Sets the spec.template.spec.hostNetwork for the Traffic Manager.\n      Set this to true when using Calico on AWS EKS to ensure that the mutating webhook can\n      communicate with the traffic manager\n    type: boolean\n\n  image:\n    type: object\n    additionalProperties: false\n    properties:\n      imagePullSecrets:\n        description: The Secret storing any credentials needed to access the image in a private registry\n        type: array\n        items:\n          $ref: \"#/$defs/localObjectReference\"\n      name:\n        description: The name of the image to use for the traffic-manager\n        type: string\n      pullPolicy:\n        description: Pull policy in the webhook for the traffic-manager image\n        $ref: \"#/$defs/pullPolicy\"\n      registry:\n        description: The registry to download the image from\n        type: string\n      tag:\n        description: Overrides the image tag whose default is the chart appVersion\n        type: string\n\n  intercept:\n    type: object\n    additionalProperties: false\n    properties:\n      allowGlobalIntercepts:\n        description: Allow global TCP/UDP intercepts. When set to false, only HTTP intercepts with header or path filters are allowed. This prevents users from creating global intercepts that block other users from intercepting the same port.\n        type: boolean\n      environment:\n        type: object\n        properties:\n          excluded:\n            description: Environment variables to exclude from the list sent to the client during engagement\n            type: array\n            items:\n              type: string\n      inactiveBlockTimeout:\n        description: >-\n          The maximum amount of time an intercept may be held by a client that is unreachable or inactive. Once this timeout is exceeded, the intercept\n          no longer blocks conflicting intercepts and may be automatically removed when another client attempts to create a conflicting intercept.\n        $ref: \"#/$defs/duration\"\n\n  isCI:\n    description: isCI can be set to force the traffic-manager namespace to be \"ambassador\"\n    type: boolean\n\n  livenessProbe:\n    description: Define livenessProbe for the traffic-manager\n    $ref: \"#/$defs/probe\"\n\n  logLevel:\n    description: Define the logging level of the Traffic Manager\n    $ref: \"#/$defs/logLevel\"\n\n  managerRbac:\n    type: object\n    additionalProperties: false\n    properties:\n      create:\n        description: Create RBAC resources for traffic-manager with this release\n        type: boolean\n      namespaces:\n        description: Declares a fixed set of managed namespaces\n        deprecated: true\n        type: array\n        items:\n          $ref: \"#/$defs/rfc1123Label\"\n\n  maxNamespaceSpecificWatchers:\n    description: >-\n      Threshold controlling when the traffic-manager switches from using watchers for each managed namespace to using\n      cluster-wide watchers\n    type: integer\n    minimum: 0\n    maximum: 50\n\n  nameOverride:\n    description: Override the default name prefix used when creating RBAC resources\n    type: string\n\n  namespaces:\n    description: Declares a fixed set of managed namespaces. Mutually exclusive to namespaceSelector\n    type: array\n    items:\n      $ref: \"#/$defs/rfc1123Label\"\n\n  namespaceSelector:\n    description: Declares the managed namespace using matchLabels and matchExpressions. Mutually exclusive to namespaces\n    $ref: \"#/$defs/labelSelector\"\n\n  nodeSelector:\n    description: Define which Nodes you want to the Traffic Manager to be deployed to.\n    $ref: \"#/$defs/nodeSelector\"\n\n  podAnnotations:\n    description: Annotations for the Traffic Manager Pod\n    type: object\n    additionalProperties:\n      type: string\n\n  podCIDRs:\n    description: podCIDRs is the verbatim list of CIDRs used when the podCIDRStrategy is set to environment\n    type: array\n    items:\n      type: string\n\n  podCIDRStrategy:\n    description: Define the strategy that the traffic-manager uses to discover what CIDRs the cluster uses for pods\n    type: string\n    enum:\n      - auto\n      - coverPodIPs\n      - environment\n      - nodePodCIDRs\n\n  podLabels:\n    description: Labels for the Traffic Manager Pod\n    type: object\n    additionalProperties:\n      type: string\n\n  podSecurityContext:\n    description: The Kubernetes SecurityContext for the traffic-manager Pod\n    $ref: \"#/$defs/podSecurityContext\"\n\n  priorityClassName:\n    description: Name of the existing pod priority class to be used\n    type: string\n\n  prometheus:\n    type: object\n    additionalProperties: false\n    properties:\n      port:\n        description: Set this port number to enable a prometheus metrics http server for the traffic manager\n        type: integer\n      dropClientLabel:\n        description: optionally drop the client label from prometheus metrics for GDPR personal data compliance\n        type: boolean\n\n  rbac:\n    type: object\n    additionalProperties: false\n    properties:\n      only:\n        description: Only create the RBAC resources and omit the traffic-manger\n        type: boolean\n\n  readinessProbe:\n    description: Define readinessProbe for the Traffic Manger.\n    $ref: \"#/$defs/probe\"\n\n  replicaCount:\n    description: Number or replicas for the traffic-manager. The Traffic Manager only support running with one replica at the moment.\n    const: 1\n\n  routeController:\n    description: >-\n      Configuration for the route-controller DaemonSet that installs blackhole routes for deleted service ClusterIPs\n      to prevent routing loops on local clusters.\n    type: object\n    additionalProperties: false\n    properties:\n      enabled:\n        description: >-\n          Enable or disable the route-controller DaemonSet. When null (the default), the\n          route-controller is automatically enabled for local clusters, detected by\n          image.registry being \"local\" or starting with \"localhost:\". Set to true to\n          force-enable or false to force-disable regardless of registry.\n        anyOf:\n          - type: \"null\"\n          - type: boolean\n      image:\n        type: object\n        additionalProperties: false\n        properties:\n          name:\n            description: The name of the route-controller image\n            type: string\n          pullPolicy:\n            description: Pull policy for the route-controller image. Empty string inherits from image.pullPolicy.\n            anyOf:\n              - type: string\n                const: \"\"\n              - $ref: \"#/$defs/pullPolicy\"\n          registry:\n            description: The registry for the route-controller image\n            type: string\n      logLevel:\n        description: Log level for the route-controller. Empty string inherits from logLevel.\n        anyOf:\n          - type: string\n            const: \"\"\n          - $ref: \"#/$defs/logLevel\"\n      serviceCIDRs:\n        description: >-\n          Service CIDRs for which subnet-level blackhole routes are installed at startup.\n          If empty, the controller queries the ServiceCIDR API (k8s >= 1.33) automatically.\n          For older clusters, set this explicitly (e.g. [\"10.96.0.0/12\"]).\n        type: array\n        items:\n          type: string\n\n  resources:\n    description: Define resource requests and limits for the Traffic Manger\n    $ref: \"#/$defs/resourceRequirements\"\n\n  schedulerName:\n    description: Specify a scheduler for Traffic Manager Pod and hooks Pod\n    type: string\n\n  securityContext:\n    description: The Kubernetes SecurityContext for the traffic-manager container\n    $ref: \"#/$defs/securityContext\"\n\n  service:\n    type: object\n    additionalProperties: false\n    properties:\n      type:\n        description: The type of service for the traffic-manager\n        type: string\n\n  startupProbe:\n    description: Define startupProbe for the Traffic Manger.\n    $ref: \"#/$defs/probe\"\n\n  telepresenceAPI:\n    type: object\n    additionalProperties: false\n    properties:\n      port:\n        description: The port on agent's localhost where the Telepresence API server can be found\n        type: integer\n\n  timeouts:\n    type: object\n    additionalProperties: false\n    properties:\n      agentArrival:\n        description: The time that the traffic-manager will wait for the traffic-agent to arrive\n        $ref: \"#/$defs/duration\"\n\n  tolerations:\n    description: Define tolerations for the Traffic Manager to ignore Node taints\n    type: array\n    items:\n      $ref: \"#/$defs/toleration\"\n\n  workloads:\n    type: object\n    additionalProperties: false\n    properties:\n      argoRollouts:\n        type: object\n        additionalProperties: false\n        properties:\n          enabled:\n            description: Enable/Disable the argo-rollouts integration\n            type: boolean\n      deployments:\n        type: object\n        additionalProperties: false\n        properties:\n          enabled:\n            description: Enable/Disable the support for Deployments\n            type: boolean\n      replicaSets:\n        type: object\n        additionalProperties: false\n        properties:\n          enabled:\n            description: Enable/Disable the support for ReplicaSets\n            type: boolean\n      statefulSets:\n        type: object\n        additionalProperties: false\n        properties:\n          enabled:\n            description: Enable/Disable the support for StatefulSets\n            type: boolean\n\n$defs:\n  duration:\n    type: string\n    pattern: \"^[+-]?(\\\\d+(\\\\.\\\\d+)?(h|m|s|ms|us|µs))+\"\n  rfc1123Label:\n    description: RFC 1123 label name\n    type: string\n    pattern: \"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$\"\n  logLevel:\n    type: string\n    enum:\n      - error\n      - warning\n      - warn\n      - info\n      - debug\n      - trace\n  pullPolicy:\n    type: string\n    enum:\n    - Always\n    - Never\n    - IfNotPresent\n  affinity:\n    $ref: \"#/definitions/io.k8s.api.core.v1.Affinity\"\n  labelSelector:\n    $ref: \"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector\"\n  localObjectReference:\n    $ref: \"#/definitions/io.k8s.api.core.v1.LocalObjectReference\"\n  nodeSelector:\n    type: object\n    additionalProperties:\n      type: string\n  resourceRequirements:\n    $ref: \"#/definitions/io.k8s.api.core.v1.ResourceRequirements\"\n  podSecurityContext:\n    $ref: \"#/definitions/io.k8s.api.core.v1.PodSecurityContext\"\n  probe:\n    $ref: \"#/definitions/io.k8s.api.core.v1.Probe\"\n  quantity:\n    $ref: \"#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity\"\n  securityContext:\n    $ref: \"#/definitions/io.k8s.api.core.v1.SecurityContext\"\n  subject:\n    $ref: \"#/definitions/io.k8s.api.rbac.v1.Subject\"\n  toleration:\n    $ref: \"#/definitions/io.k8s.api.core.v1.Toleration\"\n"
  },
  {
    "path": "charts/telepresence-oss/values.yaml",
    "content": "################################################################################\n## Deployment Configuration\n################################################################################\n\n# The Traffic Manager only support running with one replica at the moment.\n# Configuring the replicaCount will be added in future versions of Telepresence\n\nreplicaCount: 1\n\n# The Telepresence client will try to ensure that the Traffic Manager image is\n# up to date and from the right registry. If you are changing the value below,\n# ensure that the tag is the same as the client version and that the\n# TELEPRESENCE_REGISTRY environment variable is equal to image.repository.\n#\n# The client will default to ghcr.io/telepresenceio/tel2:{{CLIENT_VERSION}}\n\nimage:\n  registry: ghcr.io/telepresenceio\n  name: tel2\n  pullPolicy: IfNotPresent\n\napiPort: 8081\n\nsecurityContext:\n  readOnlyRootFilesystem: true\n  runAsNonRoot: true\n  runAsUser: 1000\n\n################################################################################\n## Traffic Manager Service Configuration\n################################################################################\n\nservice:\n  type: ClusterIP\n\n################################################################################\n## Traffic Manager Configuration\n################################################################################\n\n# The log level of the Traffic Manager.\nlogLevel: info\n\n# GRPC configuration for the Traffic Manager.\n# This is identical to the grpc configuration for local clients.\n# See https://www.telepresence.io/docs/latest/reference/config/#grpc for more info\ngrpc:\n  # Max time that the traffic-manager or traffic-agent will keep an idle client connection alive\n  connectionTTL: 24h\n\n  # maxReceiveSize is a quantity that configures the maximum message size that the traffic\n  # manager will service.\n  maxReceiveSize: 4Mi\n\n# podCIDRStrategy controls what strategy the traffic-manager will use for finding out what\n# CIDRs the cluster is using for its pods. Valid values are:\n#\n#  nodePodCIDRs extract CIDRs from the podCIDR and podCIDRs field of the Node Spec.\n#  coverPodIPs  extract IPs from the podIP and podIPs field of the Pod Status and compute the CIDRs needed to cover those IPs.\n#  environment  use CIDRs listed in the space separated POD_CIDRS environment variable verbatim.\n#  auto         first try nodePodCIDRs and if that fails, tru coverPodIPs\n#\n# Default: auto\npodCIDRStrategy: auto\n\n# maxNamespaceSpecificWatchers configures the threshold for when the traffic-manager switches from using one set of\n# watchers for each managed namespace to using cluster-wide watchers. This threshold only applies when using a\n# namespaceSelector, and only when the traffic-manager is permitted to list the cluster's namespaces.\nmaxNamespaceSpecificWatchers: 10\n\nmanagerRbac:\n  # Default: true\n  create: true\n\ntimeouts:\n  # The duration the traffic manager should wait for an agent to arrive (i.e., to be registered in the traffic manager's state)\n  # Default: 30s\n  agentArrival: 30s\n\n################################################################################\n## Agent Injector Configuration\n################################################################################\nagentInjector:\n  enabled: true\n  name: agent-injector\n  secret:\n    name: mutator-webhook-tls\n  certificate:\n     # The method used by the agent-injector to access the generated secret.\n    # Possible options: watch or mount\n    #\n    # Default watch\n    accessMethod: watch\n\n    # The method used to generate the TLS certificate for the agent-injector.\n    #\n    # Possible options: helm, supplied, or certmanager.\n    #\n    # If set to `supplied`, ensure your Secret is in the same namespace as the traffic-manager,\n    # and that `.agentInjector.secret.name` is set to its name.\n    # See the Secret in `agentInjectorWebhook.yaml` for the expected structure of the data.\n    # NOTE: If the Secret values update, the helm chart MUST be re-applied to ensure the\n    # MutatingWebhookConfiguration uses the new values.\n    #\n    # Default: helm\n    method: helm\n\n    # The certmanager configuration block\n    #\n    certmanager:\n      commonName: agent-injector\n      duration: 2160h0m0s\n      issuerRef:\n        name: telepresence\n        kind: Issuer\n\n  injectPolicy: OnDemand\n  mutationAware: true\n  webhook:\n    name: agent-injector-webhook\n    admissionReviewVersions: [\"v1\"]\n    servicePath: /traffic-agent\n    port: 8443\n    failurePolicy: Ignore\n    reinvocationPolicy: IfNeeded\n    sideEffects: None\n    timeoutSeconds: 5\n\n################################################################################\n## Telepresence traffic-agent configuration\n################################################################################\nagent:\n  port: 9900\n  enableH2cProbing: true\n  mountPolicies:\n    \"/tmp\": Local\n  image:\n    pullPolicy: IfNotPresent\n  initContainer:\n    enabled: true\n\n################################################################################\n## Telepresence API Server Configuration\n################################################################################\ntelepresenceAPI: {}\n  # The port on agent's localhost where the API service can be found\n  # Default: 0\n  # port: 0\n\n################################################################################\n## Prometheus Server Configuration\n################################################################################\nprometheus: {}\n  # Set this port number to enable a prometheus metrics http server for the\n  # traffic manager\n  # Default: 0\n  # port: 0\n  # dropClientLabel: false\n\n# Values specific to the helm chart hooks for managing upgrade/deleting\nhooks:\n  busybox:\n    registry: docker.io\n    image: busybox\n    tag: latest\n    imagePullSecrets: []\n\n  curl:\n    registry: docker.io\n    image: \"curlimages/curl\"\n    tag: 8.1.1\n    imagePullSecrets: []\n    pullPolicy: IfNotPresent\n\nclient:\n  dns:\n    # Tell client's DNS resolver to never send names with these suffixes to the cluster side resolver\n    excludeSuffixes: [\".com\", \".io\", \".net\", \".org\", \".ru\"]\n\n# Controls which workload kinds are recognized by Telepresence\nworkloads:\n  deployments:\n    enabled: true\n  replicaSets:\n    enabled: true\n  statefulSets:\n    enabled: true\n  argoRollouts:\n    enabled: false\n\n# Intercept configuration\nintercept:\n  # Allow global TCP/UDP intercepts. When set to false, only HTTP intercepts\n  # with header or path filters are allowed. This prevents users from creating\n  # global intercepts that block other users from intercepting the same port.\n  allowGlobalIntercepts: true\n\n  # The maximum amount of time an intercept may be held by a client that is unreachable or inactive.\n  # Once this timeout is exceeded, the intercept no longer blocks conflicting intercepts and may be\n  # automatically removed when another client attempts to create a conflicting intercept.\n  inactiveBlockTimeout: 10m\n\n################################################################################\n## Route Controller Configuration\n################################################################################\n# routeController installs a DaemonSet that watches for Service deletions and\n# adds temporary blackhole routes on each node for deleted ClusterIPs, preventing\n# routing loops on local clusters (Kind, minikube, k3d, Docker Desktop) where\n# deleted service IPs fall through to the node's default route.\nrouteController:\n  # enabled controls the route-controller DaemonSet:\n  #   null (default) - auto-enable when image.registry is \"local\" or starts with \"localhost:\"\n  #   true           - always enable\n  #   false          - always disable, even on local clusters\n  enabled: ~\n  image:\n    # registry and pullPolicy default to the traffic-manager image settings when not set\n    registry: \"\"\n    name: route-controller\n    pullPolicy: \"\"\n  # logLevel defaults to the traffic-manager logLevel when not set\n  logLevel: \"\"\n  # serviceCIDRs is a list of service CIDRs (e.g. [\"10.96.0.0/12\"]) for which the\n  # route-controller installs subnet-level blackhole routes at startup. These are\n  # overridden by kube-proxy for active services and catch any non-existent IP in\n  # the range without waiting for a deletion event.\n  # If empty, the controller queries the ServiceCIDR API (k8s >= 1.33) and falls\n  # back to per-IP blackhole routes on deletion for older clusters.\n  serviceCIDRs: []\n"
  },
  {
    "path": "cmd/cobraparser/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\n"
  },
  {
    "path": "cmd/cobraparser/README.md",
    "content": "# Cobra Help Output Parser\n\n## Parser\nThe `cobraparser` is a tool to parse the output produced by the `spf13.CobraCommand` help function. The parser invokes an executable with `--help` and creates JSON structured data from the resulting output. Subcommands are traversed recursively.\n\n## Generator Package\nThe `generator` package contains the code to dynamically configure a `spf13.CobraCommand` using the JSON data produced by the parser. The command will be populated with the subcommands and flags defined in the JSON data.\n\n## Sample usage:\n\nSee `build-aux/main.mk` (the `pkg/client/cli/docker/compose/dc-cli.json` target) and the corresponding use of the generator package in `pkg/client/cli/docker/compose/config.go`.\n"
  },
  {
    "path": "cmd/cobraparser/generate/generate.go",
    "content": "package generate\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/docker/docker/opts\"\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/telepresenceio/telepresence/cmd/cobraparser/v2/types\"\n)\n\n// FlagSet creates a FlagSet based on the given CommandInfo.\n//\n//nolint:gocyclo // there are a lot of cases here\nfunc FlagSet(setName string, ci *types.CommandInfo) *pflag.FlagSet {\n\tflags := pflag.NewFlagSet(setName, pflag.ContinueOnError)\n\tfor _, flag := range ci.Flags {\n\t\tswitch {\n\t\tcase flag.Type == \"bool\":\n\t\t\tval := false\n\t\t\tif flag.Default != \"\" {\n\t\t\t\tval, _ = strconv.ParseBool(flag.Default)\n\t\t\t}\n\t\t\tflags.Bool(flag.Name, val, flag.Description)\n\t\tcase flag.Type == \"boolSlice\":\n\t\t\tflags.BoolSlice(flag.Name, nil, flag.Description)\n\t\tcase flag.Type == \"duration\":\n\t\t\tval := time.Duration(0)\n\t\t\tif flag.Default != \"\" {\n\t\t\t\tval, _ = time.ParseDuration(flag.Default)\n\t\t\t}\n\t\t\tflags.Duration(flag.Name, val, flag.Description)\n\t\tcase flag.Type == \"durationSlice\":\n\t\t\tflags.DurationSlice(flag.Name, nil, flag.Description)\n\t\tcase flag.Type == \"string\":\n\t\t\tflags.String(flag.Name, flag.Default, flag.Description)\n\t\tcase flag.Type == \"stringArray\":\n\t\t\tvar val []string\n\t\t\tif flag.Default != \"\" {\n\t\t\t\tval = strings.Split(flag.Default, \",\")\n\t\t\t}\n\t\t\tflags.StringArray(flag.Name, val, flag.Description)\n\t\tcase flag.Type == \"stringSlice\":\n\t\t\tflags.StringSlice(flag.Name, nil, flag.Description)\n\t\tcase strings.HasPrefix(flag.Type, \"int\"):\n\t\t\tval := int64(0)\n\t\t\tif flag.Default != \"\" {\n\t\t\t\tval, _ = strconv.ParseInt(flag.Default, 10, 64)\n\t\t\t}\n\t\t\tswitch flag.Type {\n\t\t\tcase \"int\":\n\t\t\t\tflags.Int(flag.Name, int(val), flag.Description)\n\t\t\tcase \"int8\":\n\t\t\t\tflags.Int8(flag.Name, int8(val), flag.Description)\n\t\t\tcase \"int16\":\n\t\t\t\tflags.Int16(flag.Name, int16(val), flag.Description)\n\t\t\tcase \"int32\":\n\t\t\t\tflags.Int32(flag.Name, int32(val), flag.Description)\n\t\t\tcase \"int64\":\n\t\t\t\tflags.Int64(flag.Name, val, flag.Description)\n\t\t\tcase \"intSlice\":\n\t\t\t\tflags.IntSlice(flag.Name, nil, flag.Description)\n\t\t\tcase \"int8Slice\":\n\t\t\t\tflags.IntSlice(flag.Name, nil, flag.Description)\n\t\t\tcase \"int16Slice\":\n\t\t\t\tflags.IntSlice(flag.Name, nil, flag.Description)\n\t\t\tcase \"int32Slice\":\n\t\t\t\tflags.Int32Slice(flag.Name, nil, flag.Description)\n\t\t\tcase \"int64Slice\":\n\t\t\t\tflags.Int64Slice(flag.Name, nil, flag.Description)\n\t\t\t}\n\t\tcase strings.HasPrefix(flag.Type, \"uint\"):\n\t\t\tval := uint64(0)\n\t\t\tif flag.Default != \"\" {\n\t\t\t\tval, _ = strconv.ParseUint(flag.Default, 10, 64)\n\t\t\t}\n\t\t\tswitch flag.Type {\n\t\t\tcase \"uint\":\n\t\t\t\tflags.Uint(flag.Name, uint(val), flag.Description)\n\t\t\tcase \"uint8\":\n\t\t\t\tflags.Uint8(flag.Name, uint8(val), flag.Description)\n\t\t\tcase \"uint16\":\n\t\t\t\tflags.Uint16(flag.Name, uint16(val), flag.Description)\n\t\t\tcase \"uint32\":\n\t\t\t\tflags.Uint32(flag.Name, uint32(val), flag.Description)\n\t\t\tcase \"uint64\":\n\t\t\t\tflags.Uint64(flag.Name, val, flag.Description)\n\t\t\tcase \"uintSlice\":\n\t\t\t\tflags.UintSlice(flag.Name, nil, flag.Description)\n\t\t\t}\n\t\tcase strings.HasPrefix(flag.Type, \"float\"):\n\t\t\tval := float64(0)\n\t\t\tif flag.Default != \"\" {\n\t\t\t\tval, _ = strconv.ParseFloat(flag.Default, 64)\n\t\t\t}\n\t\t\tswitch flag.Type {\n\t\t\tcase \"float32\":\n\t\t\t\tflags.Float32(flag.Name, float32(val), flag.Description)\n\t\t\tcase \"float64\":\n\t\t\t\tflags.Float64(flag.Name, val, flag.Description)\n\t\t\tcase \"float32Slice\":\n\t\t\t\tflags.Float32Slice(flag.Name, nil, flag.Description)\n\t\t\tcase \"float564Slice\":\n\t\t\t\tflags.Float64Slice(flag.Name, nil, flag.Description)\n\t\t\t}\n\t\tcase flag.Type == \"bytes\":\n\t\t\tflags.Var(new(opts.MemBytes), flag.Name, flag.Description)\n\t\tcase flag.Type == \"filter\":\n\t\t\tflags.String(flag.Name, flag.Default, flag.Description)\n\t\tcase flag.Type == \"list\":\n\t\t\tflags.Var(opts.NewListOptsRef(new([]string), nil), flag.Name, flag.Description)\n\t\tcase flag.Type == \"scale\":\n\t\t\tflags.Uint32(flag.Name, 0, flag.Description)\n\t\tdefault:\n\t\t\tpanic(fmt.Sprintf(\"unknown flag type %s in flagset %s\", flag.Type, setName))\n\t\t}\n\t\tif flag.Shorthand != \"\" {\n\t\t\tflags.Lookup(flag.Name).Shorthand = flag.Shorthand\n\t\t}\n\t}\n\treturn flags\n}\n"
  },
  {
    "path": "cmd/cobraparser/go.mod",
    "content": "module github.com/telepresenceio/telepresence/cmd/cobraparser/v2\n\ngo 1.24\n\nrequire (\n\tgithub.com/docker/docker v28.5.2+incompatible\n\tgithub.com/spf13/pflag v1.0.10\n)\n\nrequire (\n\tgithub.com/docker/go-connections v0.6.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgotest.tools/v3 v3.5.2 // indirect\n)\n"
  },
  {
    "path": "cmd/cobraparser/go.sum",
    "content": "github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=\ngithub.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=\ngithub.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=\ngotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=\n"
  },
  {
    "path": "cmd/cobraparser/main.go",
    "content": "//go:build go1.24\n\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/telepresenceio/telepresence/cmd/cobraparser/v2/types\"\n)\n\nfunc main() {\n\tfs := pflag.NewFlagSet(\"cobraparse\", pflag.ExitOnError)\n\tvar helpArg string\n\tfs.BoolP(\"help\", \"h\", false, \"Help for this command\")\n\tfs.StringVar(&helpArg, \"help-arg\", \"--help\", \"Help for this argument\")\n\tfs.Usage = func() {\n\t\tfmt.Fprint(fs.Output(), \"Usage:\\n  cobraparse <command to parse help output from> [...args]\")\n\t\tfs.PrintDefaults()\n\t}\n\t_ = fs.Parse(os.Args[1:])\n\targs := fs.Args()\n\tif len(args) == 0 {\n\t\tfs.Usage()\n\t\tos.Exit(2)\n\t}\n\tf := types.HelpFetcher{\n\t\tExecutable: args[0],\n\t\tHelpArg:    helpArg,\n\t}\n\troot, err := f.BuildCommandTree(args[1:], 8)\n\tif err != nil {\n\t\tlog.Fatalf(\"error: %v\\n\", err)\n\t}\n\tenc := json.NewEncoder(os.Stdout)\n\tenc.SetIndent(\"\", \"  \")\n\tif err := enc.Encode(root); err != nil {\n\t\tlog.Fatalf(\"error writing JSON: %v\\n\", err)\n\t}\n}\n"
  },
  {
    "path": "cmd/cobraparser/types/commandinfo.go",
    "content": "package types\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// CommandInfo describes a command and its hierarchy.\ntype CommandInfo struct {\n\tName        string        `json:\"name\"`\n\tUsage       string        `json:\"usage,omitempty\"`\n\tDescription string        `json:\"description,omitempty\"`\n\tFlags       []FlagInfo    `json:\"flags,omitempty\"`\n\tSubcommands []CommandInfo `json:\"subcommands,omitempty\"`\n}\n\n// FlagInfo describes a flag parsed from help text.\ntype FlagInfo struct {\n\tName        string `json:\"name,omitempty\"`\n\tShorthand   string `json:\"shorthand,omitempty\"`\n\tType        string `json:\"type,omitempty\"`\n\tDescription string `json:\"description,omitempty\"`\n\tDefault     string `json:\"default,omitempty\"` // parsed from \"(default ...)\"\n\tGlobal      bool   `json:\"global,omitempty\"`\n\tInherited   bool   `json:\"inherited,omitempty\"`\n}\n\n// HelpFetcher fetches help text for a command path (root and subcommands).\ntype HelpFetcher struct {\n\tExecutable string\n\tHelpArg    string\n}\n\nfunc (f *HelpFetcher) fetch(path []string) (string, error) {\n\targs := make([]string, 0, len(path)+1)\n\targs = append(args, path...)\n\targs = append(args, f.HelpArg)\n\tcmd := exec.Command(f.Executable, args...)\n\tcmd.Env = os.Environ()\n\n\tvar buf bytes.Buffer\n\tcmd.Stdout = &buf\n\tcmd.Stderr = &buf\n\terr := cmd.Run()\n\tout := buf.String()\n\t// Some binaries return non-zero for --help; accept if output looks like help.\n\tif err != nil {\n\t\tif looksLikeHelp(out) {\n\t\t\treturn out, nil\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"error running %q %v: %w\\nOutput:\\n%s\", f, args, err, out)\n\t}\n\treturn out, nil\n}\n\nfunc looksLikeHelp(s string) bool {\n\treturn strings.Contains(s, \"Usage:\") || strings.Contains(s, \"Commands:\")\n}\n\n// Parse and traverse\n\n// BuildCommandTree recursively builds the command tree starting at path.\nfunc (f *HelpFetcher) BuildCommandTree(path []string, maxDepth int) (*CommandInfo, error) {\n\ttext, err := f.fetch(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tinfo := &CommandInfo{}\n\tif len(path) > 0 {\n\t\tinfo.Name = path[len(path)-1]\n\t} else {\n\t\tinfo.Name = f.Executable\n\t}\n\tinfo.Usage = parseUsage(text)\n\tinfo.Description = parseShortDescription(text)\n\n\tflags := make([]FlagInfo, 0, 16)\n\tflags = append(flags, parseFlagsFromSection(text, false, false, `(?m:^(?:Flags|Options):)`)...)\n\tflags = append(flags, parseFlagsFromSection(text, true, false, `(?m:^Global (?:Flags|Options):)`)...)\n\t// Cobra shows inherited flags on subcommands using this heading:\n\tflags = append(flags, parseFlagsFromSection(text, false, true, `(?m:^(?:Flags|Options) inherited from parent commands:)`)...)\n\t// Some Cobra setups may use alternate headings; add heuristics if needed.\n\tinfo.Flags = flags\n\n\t// Discover subcommands\n\tsubCommands := parseAvailableCommands(text)\n\tif len(subCommands) == 0 || maxDepth < 1 {\n\t\treturn info, nil\n\t}\n\n\tchildren := make([]CommandInfo, 0, len(subCommands))\n\tfor _, name := range subCommands {\n\t\tif name == info.Name {\n\t\t\t// We consider it highly unlikely that a command will be named the same as its parent. This assumption\n\t\t\t// prevents us from recursing down in docker swarm init [init...]\n\t\t\tcontinue\n\t\t}\n\t\tsubPath := append(path, name)\n\t\tchild, err := f.BuildCommandTree(subPath, maxDepth-1)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tchildren = append(children, *child)\n\t}\n\tinfo.Subcommands = children\n\treturn info, nil\n}\n\n// Helpers to parse sections\n\nfunc parseUsage(text string) string {\n\tfor _, line := range strings.Split(text, \"\\n\") {\n\t\tt := strings.TrimSpace(line)\n\t\tif strings.HasPrefix(t, \"Usage:\") || strings.HasPrefix(t, \"usage:\") {\n\t\t\treturn strings.TrimSpace(t[len(\"Usage:\"):])\n\t\t}\n\t}\n\treturn \"\"\n}\n\nvar afterUsagePrefixes = []string{\n\t\"Available Commands:\",\n\t\"Flags:\",\n\t\"Global Flags:\",\n\t\"Flags inherited from parent commands:\",\n\t\"Commands:\",\n\t\"Options:\",\n\t\"Global Options:\",\n\t\"Options inherited from parent commands:\",\n}\n\nfunc parseShortDescription(text string) string {\n\tfor _, t := range strings.Split(text, \"\\n\") {\n\t\tt = strings.TrimSpace(t)\n\t\tif t == \"\" || strings.HasPrefix(t, \"Usage:\") {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, prefix := range afterUsagePrefixes {\n\t\t\tif strings.HasPrefix(t, prefix) {\n\t\t\t\t// We've gone too far.\n\t\t\t\treturn \"\"\n\t\t\t}\n\t\t}\n\t\t// Take this as a description and stop.\n\t\treturn t\n\t}\n\treturn \"\"\n}\n\nfunc parseAvailableCommands(text string) []string {\n\tvar res []string\n\tfor {\n\t\ttext = sliceAfter(text, `(?m:^(?:[A-Z][0-9A-Za-z-]*\\s+)?Commands:)`)\n\t\tif text == \"\" {\n\t\t\tbreak\n\t\t}\n\t\tfor _, line := range strings.Split(text, \"\\n\") {\n\t\t\ttrim := strings.TrimSpace(line)\n\t\t\tif trim == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !startsWithIndent(line) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tname := trim\n\t\t\tspaceIdx := strings.IndexAny(trim, \" \\t\\n\")\n\t\t\tif spaceIdx > 0 {\n\t\t\t\tname = trim[:spaceIdx]\n\t\t\t}\n\t\t\tname = strings.TrimSuffix(name, \"*\")\n\t\t\tres = append(res, name)\n\t\t}\n\t}\n\treturn res\n}\n\nfunc startsWithIndent(s string) bool {\n\treturn strings.HasPrefix(s, \"  \") || strings.HasPrefix(s, \"\\t\")\n}\n\nvar (\n\t// Matches lines like:\n\t//   -n, --name string   description (default \"foo\")\n\t//       --verbose       description\n\t//       --count int     description (default 3)\n\t//   -q                   description\n\tflagLineRe = regexp.MustCompile(`^(?:-([A-Za-z0-9]),\\s*)?(?:--([A-Za-z0-9][A-Za-z0-9-]*))?(?:\\s+(\\S+))?\\s{2,}(.+)$`)\n\tdefaultRe  = regexp.MustCompile(`^(.*)\\s*\\((?i:default)[:\\s]*([^)]+)\\)`)\n)\n\nfunc parseFlagsFromSection(text string, global, inherited bool, headerPattern string) []FlagInfo {\n\tsection := sliceAfter(text, headerPattern)\n\tif section == \"\" {\n\t\treturn nil\n\t}\n\tlines := strings.Split(section, \"\\n\")\n\tvar flags []FlagInfo\n\tfor _, line := range lines {\n\t\t// Expect flag lines to be indented.\n\t\ttrim := strings.TrimSpace(line)\n\t\tif trim == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif !startsWithIndent(line) {\n\t\t\tbreak\n\t\t}\n\t\tline = trim\n\t\tm := flagLineRe.FindStringSubmatch(line)\n\t\tif len(m) == 0 {\n\t\t\t// Indented line that doesn't match the flag regexp belongs to the previous message\n\t\t\tif len(flags) > 0 {\n\t\t\t\tflags[len(flags)-1].Description += \" \" + line\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tshort := m[1]\n\t\tlong := m[2]\n\t\ttyp := m[3]\n\t\tif typ == \"\" {\n\t\t\ttyp = \"bool\"\n\t\t}\n\t\tdesc := strings.TrimSpace(m[4])\n\n\t\tif long == \"\" && short == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tdflt := \"\"\n\t\tif mm := defaultRe.FindStringSubmatch(desc); len(mm) == 3 {\n\t\t\t// This might be a \"fake\" default, i.e. a default that is embedded in the description rather\n\t\t\t// than a true default for the flag. We don't want defaults that are unparseable.\n\t\t\tdflt = strings.TrimSpace(mm[2])\n\t\t\tswitch typ {\n\t\t\tcase \"int\", \"int16\", \"int32\", \"int64\", \"uint16\", \"uint32\", \"uint64\":\n\t\t\t\tif _, err := strconv.ParseInt(dflt, 10, 64); err != nil {\n\t\t\t\t\tdflt = \"\"\n\t\t\t\t}\n\t\t\tcase \"bool\":\n\t\t\t\tif _, err := strconv.ParseBool(dflt); err != nil {\n\t\t\t\t\tdflt = \"\"\n\t\t\t\t}\n\t\t\tcase \"duration\":\n\t\t\t\tif _, err := time.ParseDuration(dflt); err != nil {\n\t\t\t\t\tdflt = \"\"\n\t\t\t\t}\n\t\t\tcase \"float32\", \"float64\":\n\t\t\t\tif _, err := strconv.ParseFloat(dflt, 64); err != nil {\n\t\t\t\t\tdflt = \"\"\n\t\t\t\t}\n\t\t\tcase \"string\":\n\t\t\t\tif len(dflt) < 3 || dflt[0] != '\"' || dflt[len(dflt)-1] != '\"' {\n\t\t\t\t\tdflt = \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tif dflt != \"\" {\n\t\t\t\tdesc = strings.TrimSpace(mm[1])\n\t\t\t}\n\t\t}\n\n\t\tflags = append(flags, FlagInfo{\n\t\t\tName:        long,\n\t\t\tShorthand:   short,\n\t\t\tType:        typ,\n\t\t\tDescription: desc,\n\t\t\tDefault:     dflt,\n\t\t\tGlobal:      global,\n\t\t\tInherited:   inherited,\n\t\t})\n\t}\n\treturn flags\n}\n\nfunc sliceAfter(text, headerPattern string) string {\n\tif ixs := regexp.MustCompile(headerPattern).FindStringIndex(text); ixs != nil {\n\t\treturn text[ixs[1]:]\n\t}\n\treturn \"\"\n}\n"
  },
  {
    "path": "cmd/routecontroller/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/coreos/go-iptables/iptables\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\n\t\"github.com/telepresenceio/clog\"\n\ttplog \"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/sigctx\"\n)\n\n// subnetRule tracks an iptables FORWARD DROP rule installed for a service CIDR.\ntype subnetRule struct {\n\tipt  *iptables.IPTables\n\tcidr string\n}\n\nfunc main() {\n\tctx := context.Background()\n\tlogLevel := os.Getenv(\"LOG_LEVEL\")\n\tctx = tplog.MakeBaseLogger(ctx, os.Stdout, logLevel)\n\n\tif err := sigctx.DoWithSignalHandler(ctx, run); err != nil {\n\t\tclog.Error(ctx, err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc run(ctx context.Context) error {\n\tcfg, err := rest.InClusterConfig()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get in-cluster config: %w\", err)\n\t}\n\n\tcs, err := kubernetes.NewForConfig(cfg)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create clientset: %w\", err)\n\t}\n\n\trules := installSubnetBlackholes(ctx, cs)\n\tdefer removeSubnetBlackholes(ctx, rules)\n\n\tclog.Info(ctx, \"Route controller started\")\n\t<-ctx.Done()\n\treturn nil\n}\n\n// discoverServiceCIDRs returns the service CIDRs for which iptables FORWARD DROP rules\n// will be installed. It first checks the SERVICE_CIDRS environment variable\n// (comma-separated list of CIDRs). If that is not set, it queries the Kubernetes\n// ServiceCIDR API (available in k8s 1.33+). If neither is available it returns nil and\n// no subnet-level protection is installed; set SERVICE_CIDRS explicitly in that case.\nfunc discoverServiceCIDRs(ctx context.Context, cs *kubernetes.Clientset) []string {\n\tif envCIDRs := os.Getenv(\"SERVICE_CIDRS\"); envCIDRs != \"\" {\n\t\tcidrs := strings.Split(envCIDRs, \",\")\n\t\tclog.Infof(ctx, \"Using service CIDRs from SERVICE_CIDRS env: %v\", cidrs)\n\t\treturn cidrs\n\t}\n\n\tlist, err := cs.NetworkingV1().ServiceCIDRs().List(ctx, meta.ListOptions{})\n\tif err != nil {\n\t\tclog.Warnf(ctx, \"ServiceCIDR API unavailable: %v; set SERVICE_CIDRS to enable subnet blackholing\", err)\n\t\treturn nil\n\t}\n\tvar cidrs []string\n\tfor _, item := range list.Items {\n\t\tcidrs = append(cidrs, item.Spec.CIDRs...)\n\t}\n\tclog.Infof(ctx, \"Discovered service CIDRs from cluster API: %v\", cidrs)\n\treturn cidrs\n}\n\n// installSubnetBlackholes installs iptables FORWARD chain DROP rules for each service CIDR.\n//\n// An iptables rule in the FORWARD chain (rather than a kernel blackhole route) is used\n// because RTN_BLACKHOLE routes fail connect()/sendmsg() at the socket level before\n// any iptables hook can fire, which breaks locally-generated traffic such as\n// kube-apiserver → mutating-webhook calls.\n//\n// The FORWARD chain only affects traffic forwarded through the host (i.e. pod traffic via\n// veth pairs). Locally-generated host traffic is never subject to the FORWARD chain.\n//\n// For active services, kube-proxy's PREROUTING DNAT fires before the FORWARD chain and\n// rewrites the destination from ClusterIP to a pod IP, so the DROP rule (which matches\n// on the original service CIDR) does not apply. For deleted or never-assigned ClusterIPs\n// no DNAT rule exists, the FORWARD chain sees the original ClusterIP, and the DROP fires.\nfunc installSubnetBlackholes(ctx context.Context, cs *kubernetes.Clientset) []subnetRule {\n\tvar rules []subnetRule\n\tfor _, cidr := range discoverServiceCIDRs(ctx, cs) {\n\t\tcidr = strings.TrimSpace(cidr)\n\t\t_, network, err := net.ParseCIDR(cidr)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"Failed to parse service CIDR %q: %v\", cidr, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tproto := iptables.ProtocolIPv4\n\t\tif network.IP.To4() == nil {\n\t\t\tproto = iptables.ProtocolIPv6\n\t\t}\n\n\t\tipt, err := iptables.NewWithProtocol(proto)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"Failed to initialise iptables for %s: %v\", network, err)\n\t\t\tcontinue\n\t\t}\n\n\t\texists, err := ipt.Exists(\"filter\", \"FORWARD\", \"-d\", network.String(), \"-j\", \"DROP\")\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"Failed to check iptables FORWARD rule for %s: %v\", network, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !exists {\n\t\t\tif err := ipt.Insert(\"filter\", \"FORWARD\", 1, \"-d\", network.String(), \"-j\", \"DROP\"); err != nil {\n\t\t\t\tclog.Errorf(ctx, \"Failed to add iptables FORWARD DROP for service CIDR %s: %v\", network, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tclog.Infof(ctx, \"Added iptables FORWARD DROP for service CIDR %s\", network)\n\t\t} else {\n\t\t\tclog.Debugf(ctx, \"iptables FORWARD DROP for %s already present\", network)\n\t\t}\n\n\t\trules = append(rules, subnetRule{ipt: ipt, cidr: network.String()})\n\t}\n\treturn rules\n}\n\nfunc removeSubnetBlackholes(ctx context.Context, rules []subnetRule) {\n\tfor _, rule := range rules {\n\t\tif err := rule.ipt.Delete(\"filter\", \"FORWARD\", \"-d\", rule.cidr, \"-j\", \"DROP\"); err != nil {\n\t\t\tclog.Debugf(ctx, \"Failed to remove iptables FORWARD DROP for %s: %v\", rule.cidr, err)\n\t\t} else {\n\t\t\tclog.Infof(ctx, \"Removed iptables FORWARD DROP for service CIDR %s\", rule.cidr)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "cmd/teleroute/.gitignore",
    "content": "rpc/"
  },
  {
    "path": "cmd/teleroute/DEVELOPING.md",
    "content": "# Docker network plugin for Telepresence\n\n## Debugging\n\nStart by configuring telepresence to not check for the latest version of the plugin, but instead use our debug version by\nadding the following yaml to the `config.yml` (on Linux, this will be in `~/.config/telepresence/config.yml`, and on mac\nyou'll find it in `\"$HOME/Library/Application Support/telepresence/config.yml\"`:\n```yaml\nintercept:\n  teleroute:\n    tag: debug\n```\n\nBuild the plugin for debugging. The command both builds and enables the plugin:\n```console\n$ make debug\n```\n\nUse runc to tail the plugin's log output\n\n```console\nsudo runc --root /run/docker/runtime-runc/plugins.moby exec $(docker plugin list --no-trunc -f capability=networkdriver -f enabled=true -q) tail -n 400 -f /var/log/teleroute.log\n```\n"
  },
  {
    "path": "cmd/teleroute/Dockerfile",
    "content": "FROM --platform=$BUILDPLATFORM golang:alpine AS builder\n\nRUN apk add --no-cache --virtual .build-deps gcc libc-dev\nWORKDIR /docker-network-teleroute\nCOPY . .\nARG TARGETOS\nARG TARGETARCH\nRUN \\\n    --mount=type=cache,target=/root/.cache/go-build \\\n    --mount=type=cache,target=/go/pkg/mod \\\n    GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /go/bin/docker-network-teleroute\n\nFROM alpine\nRUN apk add --no-cache bash\nCOPY --from=builder /go/bin/docker-network-teleroute /bin/\n"
  },
  {
    "path": "cmd/teleroute/Makefile",
    "content": "# Avoid potential confusion about what shell to use\nSHELL := /bin/bash\n\n# Delete implicit rules not used here (clutters debug output).\nMAKEFLAGS += --no-builtin-rules\n\n# Turn off .INTERMEDIATE file removal by marking all files as\n# .SECONDARY.  .INTERMEDIATE file removal is a space-saving hack from\n# a time when drives were small; on modern computers with plenty of\n# storage, it causes nothing but headaches.\n#\n# https://news.ycombinator.com/item?id=16486331\n.SECONDARY:\n\n# If a recipe errors, remove the target it was building.  This\n# prevents outdated/incomplete results of failed runs from tainting\n# future runs.  The only reason .DELETE_ON_ERROR is off by default is\n# for historical compatibility.\n#\n# If for some reason this behavior is not desired for a specific\n# target, mark that target as .PRECIOUS.\n.DELETE_ON_ERROR:\n\nPLUGIN_ARCH ?= $(shell go env GOARCH)\nPLUGIN_ARCH := $(PLUGIN_ARCH)\nPLUGIN_REGISTRY ?= ghcr.io/telepresenceio\nPLUGIN_NAME = teleroute\nPLUGIN_FQN = $(PLUGIN_REGISTRY)/$(PLUGIN_NAME)\nPLUGIN_DEV_IMAGE = $(PLUGIN_FQN):$(PLUGIN_ARCH)\n\nBUILD_DIR=build-output\n\nexport DOCKER_BUILDKIT := 1\n\n.PHONY: clean\nclean:\n\trm -rf $(BUILD_DIR) rpc\n\nrpc/teleroute/.rsync-stamp: ../../rpc/go.mod ../../rpc/go.sum $(wildcard ../../rpc/teleroute/*.go)\n\tmkdir -p rpc/teleroute\n\trsync -au --delete ../../rpc/go.mod rpc/\n\trsync -au --delete ../../rpc/go.sum rpc/\n\trsync -au --delete ../../rpc/teleroute rpc/\n\ttouch rpc/teleroute/.rsync-stamp\n\n.PHONY: rootfs\nrootfs: rpc/teleroute/.rsync-stamp\n\tdocker buildx inspect |grep -q /$(PLUGIN_ARCH) || \\\n\tdocker run --rm --privileged tonistiigi/binfmt --install all\n\trm -rf $(BUILD_DIR)\n\tdocker buildx build --platform linux/$(PLUGIN_ARCH) --output $(BUILD_DIR)/rootfs .\n\tcp config.json $(BUILD_DIR)\n\n# Enable is to support faster dev-loop (retries without pushing)\n.PHONY: enable\nenable: rootfs\n\tdocker plugin rm --force $(PLUGIN_DEV_IMAGE) 2>/dev/null || true\n\tdocker plugin create $(PLUGIN_DEV_IMAGE) $(BUILD_DIR)\n\tdocker plugin enable $(PLUGIN_DEV_IMAGE)\n\n.PHONY: debug\ndebug: rootfs\n\tdocker plugin rm --force $(PLUGIN_DEV_IMAGE)-debug 2>/dev/null || true\n\tdocker plugin create $(PLUGIN_DEV_IMAGE)-debug $(BUILD_DIR)\n\tdocker plugin set $(PLUGIN_DEV_IMAGE)-debug DEBUG=true\n\tdocker plugin enable $(PLUGIN_DEV_IMAGE)-debug\n\n# Recreate the plugin from the rootfs with some tag. This target is called\n# repeatedly in order to give the plugin different tags (because plugins cannot\n# be tagged like images).\n$(BUILD_DIR)/tag-%.ts:\n\tdocker plugin rm --force $(PLUGIN_FQN):$* 2>/dev/null || true\n\tdocker plugin create $(PLUGIN_FQN):$* $(BUILD_DIR)\n\tdocker plugin push $(PLUGIN_FQN):$*\n\tdocker plugin rm --force $(PLUGIN_FQN):$* 2>/dev/null || true\n\ttouch $(BUILD_DIR)/tag-$*.ts\n\ngaPattern = ^[0-9]+.[0-9]+.[0-9]+$$\n\n# For amd64 we push the tags \"amd64-SEMVER\" and \"SEMVER\", and if it is a release, also \"amd64\" and \"latest\".\n# For arm64 we push the tag \"arm64-SEMVER\", and if it is a release also \"arm64\".\n.PHONY: push\nifeq ($(PLUGIN_ARCH), amd64)\npush:  clean rootfs $(BUILD_DIR)/tag-$(PLUGIN_ARCH)-$(PLUGIN_VERSION).ts $(BUILD_DIR)/tag-$(PLUGIN_VERSION).ts\nelse\npush:  clean rootfs $(BUILD_DIR)/tag-$(PLUGIN_ARCH)-$(PLUGIN_VERSION).ts\nendif\n\tif [[ $(PLUGIN_VERSION) =~ $(gaPattern) ]]; \\\n\tthen \\\n\t  make push-latest; \\\n\tfi\n\n.PHONY: push-latest\nifeq ($(PLUGIN_ARCH), amd64)\npush-latest:  $(BUILD_DIR)/tag-$(PLUGIN_ARCH).ts $(BUILD_DIR)/tag-latest.ts\nelse\npush-latest:  $(BUILD_DIR)/tag-$(PLUGIN_ARCH).ts\nendif\n"
  },
  {
    "path": "cmd/teleroute/config.json",
    "content": "{\n  \"description\": \"Docker network plugin for Telepresence\",\n  \"documentation\": \"https://docs.docker.com/engine/extend/plugins/\",\n  \"entrypoint\": [\n    \"/bin/docker-network-teleroute\"\n  ],\n  \"env\": [\n    {\n      \"name\": \"DEBUG\",\n      \"settable\": [\n        \"value\"\n      ],\n      \"value\": \"false\"\n    }\n  ],\n  \"interface\": {\n    \"socket\": \"teleroute.sock\",\n    \"types\": [\n      \"docker.networkdriver/1.0\"\n    ]\n  },\n  \"pidhost\": true,\n  \"linux\": {\n    \"capabilities\": [\n      \"CAP_NET_ADMIN\"\n    ]\n  },\n  \"network\": {\n    \"type\": \"host\"\n  }\n }\n"
  },
  {
    "path": "cmd/teleroute/driver/driver.go",
    "content": "package driver\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/docker/go-plugins-helpers/network\"\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\ntype driver struct {\n\tctx      context.Context\n\tpid      int\n\tnetworks *xsync.Map[string, *networkState]\n}\n\nfunc New(ctx context.Context, pid int) network.Driver {\n\treturn &driver{\n\t\tctx:      ctx,\n\t\tpid:      pid,\n\t\tnetworks: xsync.NewMap[string, *networkState](),\n\t}\n}\n\ntype errNetworkNotFound string\n\nfunc (e errNetworkNotFound) Error() string {\n\treturn fmt.Sprintf(\"network %q not found\", string(e))\n}\n\nfunc (d *driver) GetCapabilities() (*network.CapabilitiesResponse, error) {\n\tclog.Debug(d.ctx, \"GetCapabilities\")\n\treturn &network.CapabilitiesResponse{\n\t\tScope: network.LocalScope,\n\t}, nil\n}\n\nfunc (d *driver) CreateNetwork(r *network.CreateNetworkRequest) error {\n\tn, err := newNetwork(d.ctx, d.pid, r)\n\tif err != nil {\n\t\treturn err\n\t}\n\td.networks.Store(r.NetworkID, n)\n\treturn nil\n}\n\nfunc (d *driver) DeleteNetwork(r *network.DeleteNetworkRequest) error {\n\tif n, ok := d.networks.LoadAndDelete(r.NetworkID); ok {\n\t\tclog.Debug(n.ctx, \"Deleting network\")\n\t\tn.cancel()\n\t}\n\treturn nil\n}\n\nfunc (d *driver) CreateEndpoint(r *network.CreateEndpointRequest) (*network.CreateEndpointResponse, error) {\n\tn, ok := d.networks.Load(r.NetworkID)\n\tif !ok {\n\t\treturn nil, errNetworkNotFound(r.NetworkID)\n\t}\n\treturn n.createEndpoint(r)\n}\n\nfunc (d *driver) DeleteEndpoint(r *network.DeleteEndpointRequest) error {\n\treturn d.withNetwork(r.NetworkID, func(ns *networkState) error { return ns.deleteEndpoint(r.EndpointID) })\n}\n\nfunc (d *driver) Join(r *network.JoinRequest) (*network.JoinResponse, error) {\n\tn, ok := d.networks.Load(r.NetworkID)\n\tif !ok {\n\t\treturn nil, errNetworkNotFound(r.NetworkID)\n\t}\n\treturn n.join(r)\n}\n\nfunc (d *driver) Leave(r *network.LeaveRequest) (err error) {\n\treturn d.withNetwork(r.NetworkID, func(ns *networkState) error { return ns.leaveEndpoint(r.EndpointID) })\n}\n\nfunc (d *driver) EndpointInfo(r *network.InfoRequest) (*network.InfoResponse, error) {\n\treturn nil, nil\n}\n\nfunc (d *driver) DiscoverNew(*network.DiscoveryNotification) error {\n\treturn nil\n}\n\nfunc (d *driver) DiscoverDelete(*network.DiscoveryNotification) error {\n\treturn nil\n}\n\nfunc (d *driver) ProgramExternalConnectivity(*network.ProgramExternalConnectivityRequest) error {\n\treturn nil\n}\n\nfunc (d *driver) RevokeExternalConnectivity(*network.RevokeExternalConnectivityRequest) error {\n\treturn nil\n}\n\nfunc (d *driver) AllocateNetwork(*network.AllocateNetworkRequest) (*network.AllocateNetworkResponse, error) {\n\treturn nil, nil\n}\n\nfunc (d *driver) FreeNetwork(*network.FreeNetworkRequest) error {\n\treturn nil\n}\n\nfunc (d *driver) withNetwork(id string, f func(*networkState) error) (err error) {\n\tn, ok := d.networks.Load(id)\n\tif !ok {\n\t\treturn errNetworkNotFound(id)\n\t}\n\treturn f(n)\n}\n"
  },
  {
    "path": "cmd/teleroute/driver/network.go",
    "content": "package driver\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/docker/go-plugins-helpers/network\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/proto\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/teleroute\"\n)\n\ntype networkState struct {\n\toptions\n\n\tctx context.Context\n\n\tcancel context.CancelFunc\n\n\tpid int\n\n\t// clientConn is connected to the Telepresence daemon telemount gRPC.\n\tclientConn *grpc.ClientConn\n}\n\nfunc newNetwork(ctx context.Context, pid int, r *network.CreateNetworkRequest) (n *networkState, err error) {\n\tctx, cancel := context.WithCancel(ctx)\n\tctx = clog.WithGroup(ctx, r.NetworkID[:12])\n\tclog.Debugf(ctx, \"CreateNetwork, %v\", r.Options)\n\tn = &networkState{\n\t\tctx:    ctx,\n\t\tcancel: cancel,\n\t\tpid:    pid,\n\t}\n\terr = n.initialize(r)\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\treturn nil, err\n\t}\n\treturn n, nil\n}\n\nfunc (n *networkState) initialize(r *network.CreateNetworkRequest) (err error) {\n\tvar gws []netip.Prefix\n\tfor _, ia := range r.IPv4Data {\n\t\tif ia.Gateway != \"\" {\n\t\t\tgw, err := netip.ParsePrefix(ia.Gateway)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid IPv4 gateway address: %s\", ia.Gateway)\n\t\t\t}\n\t\t\tgws = append(gws, gw)\n\t\t}\n\t}\n\tfor _, ia := range r.IPv6Data {\n\t\tif ia.Gateway != \"\" {\n\t\t\tgw, err := netip.ParsePrefix(ia.Gateway)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"invalid IPv4 gateway address: %s\", ia.Gateway)\n\t\t\t}\n\t\t\tgws = append(gws, gw)\n\t\t}\n\t}\n\tdriverOpts, ok := r.Options[\"com.docker.network.generic\"].(map[string]any)\n\tif !ok {\n\t\treturn fmt.Errorf(\"network options are missing com.docker.network.generic\")\n\t}\n\terr = n.options.parse(driverOpts)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttime.Sleep(time.Second)\n\treturn n.connectToDaemon(gws)\n}\n\n// callDaemon adds a short timeout when calling the daemon's gRPC so that we don't run into long waits in case something goes wrong.\nfunc callDaemon[R proto.Message](ns *networkState, f func(ctx context.Context, client teleroute.TelerouteClient) (R, error)) (rsp R, err error) {\n\tctx, cancel := context.WithTimeout(ns.ctx, 3*time.Second)\n\trsp, err = f(ctx, teleroute.NewTelerouteClient(ns.clientConn))\n\tcancel()\n\treturn rsp, err\n}\n\nfunc (n *networkState) connectToDaemon(gateways []netip.Prefix) (err error) {\n\tap := netip.AddrPortFrom(n.host, n.port)\n\tif n.clientConn != nil {\n\t\tn.clientConn.Close()\n\t}\n\n\tn.clientConn, err = grpc.NewClient(ap.String(), grpc.WithTransportCredentials(insecure.NewCredentials()))\n\tif err != nil {\n\t\terr = fmt.Errorf(\"unable to create gRPC connection to daemon: %w\", err)\n\t\tclog.Error(n.ctx, err)\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tn.clientConn.Close()\n\t\t}\n\t}()\n\n\tgws := make([][]byte, len(gateways))\n\tfor i, gw := range gateways {\n\t\tgws[i], err = gw.MarshalBinary()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tclient := teleroute.NewTelerouteClient(n.clientConn)\n\trq := &teleroute.ConnectRequest{Pid: int64(n.pid), Gateways: gws}\n\n\tvar infoStream grpc.ServerStreamingClient[teleroute.Info]\n\terr = backoff.Retry(func() error {\n\t\tinfoStream, err = client.Connect(n.ctx, rq)\n\t\tif status.Code(err) != codes.Unavailable {\n\t\t\terr = backoff.Permanent(err)\n\t\t}\n\t\treturn err\n\t}, backoff.WithMaxRetries(backoff.NewConstantBackOff(50*time.Millisecond), 20))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tgo func() {\n\t\tdefer n.cancel()\n\t\tfor {\n\t\t\tinfo, err := infoStream.Recv()\n\t\t\tif err != nil {\n\t\t\t\tif !errors.Is(err, io.EOF) {\n\t\t\t\t\tclog.Errorf(n.ctx, \"error receiving info: %v\", err)\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tim := info.GetInfo()\n\t\t\tclog.Infof(n.ctx, \"Connected to %s version %s\", im[\"name\"], im[\"version\"])\n\t\t}\n\t}()\n\treturn nil\n}\n\nfunc rawAddrFromPrefixString(s string) (rawAddr []byte, err error) {\n\tif len(s) == 0 {\n\t\treturn nil, nil\n\t}\n\tvar pfx netip.Prefix\n\tpfx, err = netip.ParsePrefix(s)\n\tif err == nil {\n\t\trawAddr, err = pfx.Addr().MarshalBinary()\n\t}\n\tif err != nil {\n\t\terr = fmt.Errorf(\"invalid CIDR %q: %w\", s, err)\n\t}\n\treturn rawAddr, err\n}\n\nfunc (n *networkState) createEndpoint(r *network.CreateEndpointRequest) (_ *network.CreateEndpointResponse, err error) {\n\tclog.Debugf(n.ctx, \"Create endpoint %.8s %v %v\", r.EndpointID, r.Interface, r.Options)\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tclog.Error(n.ctx, err)\n\t\t}\n\t}()\n\n\trequest := &teleroute.CreateEndpointRequest{\n\t\tId: r.EndpointID,\n\t}\n\trequest.AddrIpv4, err = rawAddrFromPrefixString(r.Interface.Address)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trequest.AddrIpv6, err = rawAddrFromPrefixString(r.Interface.AddressIPv6)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif daemonOpt, ok := r.Options[\"daemon\"].(string); ok {\n\t\trequest.Daemon, _ = strconv.ParseBool(daemonOpt)\n\t}\n\t_, err = callDaemon(n, func(ctx context.Context, client teleroute.TelerouteClient) (*emptypb.Empty, error) {\n\t\treturn client.CreateEndpoint(ctx, request)\n\t})\n\treturn &network.CreateEndpointResponse{}, err\n}\n\nfunc (n *networkState) join(r *network.JoinRequest) (response *network.JoinResponse, err error) {\n\tendpointID := r.EndpointID\n\tclog.Debugf(n.ctx, \"Join endpoint %.8s, sandbox %.8s %v\", endpointID, r.SandboxKey, r.Options)\n\n\trsp, err := callDaemon(n, func(ctx context.Context, client teleroute.TelerouteClient) (*teleroute.JoinResponse, error) {\n\t\treturn client.Join(ctx, &teleroute.EndpointIdentifier{\n\t\t\tId: endpointID,\n\t\t})\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trouteType := 1\n\tvar viaStr string\n\tif len(rsp.Via) > 0 {\n\t\tvar nxt netip.Addr\n\t\terr = nxt.UnmarshalBinary(rsp.Via)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to unmarshal via with length %d: %w\", len(rsp.Via), err)\n\t\t}\n\t\tviaStr = nxt.String()\n\t\trouteType = 0\n\t}\n\tsrs := make([]*network.StaticRoute, len(rsp.Routes))\n\tfor i, r := range rsp.Routes {\n\t\tvar pfx netip.Prefix\n\t\terr = pfx.UnmarshalBinary(r)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to unmarshal route with length %d: %w\", len(r), err)\n\t\t}\n\t\tsrs[i] = &network.StaticRoute{\n\t\t\tDestination: pfx.String(),\n\t\t\tRouteType:   routeType,\n\t\t\tNextHop:     viaStr,\n\t\t}\n\t}\n\tresponse = &network.JoinResponse{\n\t\tInterfaceName: network.InterfaceName{\n\t\t\tSrcName:   rsp.InterfaceSrcName,\n\t\t\tDstPrefix: rsp.InterfaceDstPrefix,\n\t\t},\n\t\tStaticRoutes:          srs,\n\t\tDisableGatewayService: rsp.DisableGw,\n\t}\n\tif len(rsp.GwIpV4) > 0 {\n\t\tvar gwIPv4 netip.Prefix\n\t\terr = gwIPv4.UnmarshalBinary(rsp.GwIpV4)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to unmarshal GwIpV4 with length %d: %w\", len(rsp.GwIpV4), err)\n\t\t}\n\t\tresponse.Gateway = gwIPv4.Addr().String()\n\t}\n\tif len(rsp.GwIpV6) > 0 {\n\t\tvar gwIPv6 netip.Prefix\n\t\terr = gwIPv6.UnmarshalBinary(rsp.GwIpV6)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to unmarshal GwIpV6 with length %d: %w\", len(rsp.GwIpV6), err)\n\t\t}\n\t\tresponse.GatewayIPv6 = gwIPv6.Addr().String()\n\t}\n\tclog.Debugf(n.ctx, \"Join response %v\", response)\n\treturn response, nil\n}\n\nfunc (n *networkState) leaveEndpoint(endpointID string) error {\n\tclog.Debugf(n.ctx, \"Leave endpoint %.8s\", endpointID)\n\t_, err := callDaemon(n, func(ctx context.Context, client teleroute.TelerouteClient) (*emptypb.Empty, error) {\n\t\treturn client.Leave(ctx, &teleroute.EndpointIdentifier{\n\t\t\tId: endpointID,\n\t\t})\n\t})\n\tif err != nil {\n\t\tcode := status.Code(err)\n\t\tif code == codes.Canceled || code == codes.Unavailable {\n\t\t\terr = nil\n\t\t} else {\n\t\t\tclog.Errorf(n.ctx, \"failed to leave endpoint %s: %v\", endpointID, err)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (n *networkState) deleteEndpoint(endpointID string) error {\n\tclog.Debugf(n.ctx, \"Delete endpoint %.8s\", endpointID)\n\t_, err := callDaemon(n, func(ctx context.Context, client teleroute.TelerouteClient) (*emptypb.Empty, error) {\n\t\treturn client.RemoveEndpoint(ctx, &teleroute.EndpointIdentifier{Id: endpointID})\n\t})\n\tif err != nil {\n\t\tcode := status.Code(err)\n\t\tif code == codes.Canceled || code == codes.Unavailable {\n\t\t\terr = nil\n\t\t} else {\n\t\t\tclog.Errorf(n.ctx, \"failed to leave endpoint %s: %v\", endpointID, err)\n\t\t}\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "cmd/teleroute/driver/options.go",
    "content": "package driver\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strconv\"\n)\n\ntype options struct {\n\t// host IP on the default bridge network for the Telepresence daemon\n\thost netip.Addr\n\n\t// port where the Telepresence daemon starts its teleroute server\n\tport uint16\n}\n\ntype errRequiredOption string\n\nfunc (e errRequiredOption) Error() string {\n\treturn fmt.Sprintf(\"option %q is required\", e)\n}\n\nfunc (o *options) parse(gos map[string]any) error {\n\tunableToParse := func(opt string, err error) error {\n\t\treturn fmt.Errorf(\"unable to parse %q: %w\", opt, err)\n\t}\n\tfor key, anyVal := range gos {\n\t\tval, ok := anyVal.(string)\n\t\tif !ok {\n\t\t\treturn unableToParse(key, fmt.Errorf(\"invalid value type %T\", anyVal))\n\t\t}\n\t\tvar err error\n\t\tswitch key {\n\t\tcase \"host\":\n\t\t\to.host, err = netip.ParseAddr(val)\n\t\t\tif err != nil {\n\t\t\t\treturn unableToParse(key, err)\n\t\t\t}\n\t\tcase \"port\":\n\t\t\tvar port uint64\n\t\t\tif port, err = strconv.ParseUint(val, 10, 16); err == nil {\n\t\t\t\tif port == 0 {\n\t\t\t\t\treturn unableToParse(key, errors.New(\"cannot be 0\"))\n\t\t\t\t}\n\t\t\t\to.port = uint16(port)\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"illegal option %q\", key)\n\t\t}\n\t}\n\tif !o.host.IsValid() {\n\t\treturn errRequiredOption(\"host\")\n\t}\n\tif o.port == 0 {\n\t\treturn errRequiredOption(\"port\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "cmd/teleroute/go.mod",
    "content": "module github.com/telepresenceio/telepresence/cmd/teleroute\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/cenkalti/backoff/v4 v4.3.0\n\tgithub.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8\n\tgithub.com/puzpuzpuz/xsync/v4 v4.4.0\n\tgithub.com/telepresenceio/clog v0.0.0-20260114221933-287514cf9831\n\tgithub.com/telepresenceio/telepresence/rpc/v2 v2.26.2\n\tgoogle.golang.org/grpc v1.79.1\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect\n\tgithub.com/docker/go-connections v0.6.0 // indirect\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect\n)\n\nreplace github.com/telepresenceio/telepresence/rpc/v2 => ./rpc\n"
  },
  {
    "path": "cmd/teleroute/go.sum",
    "content": "github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\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/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=\ngithub.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=\ngithub.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=\ngithub.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8 h1:IMfrF5LCzP2Vhw7j4IIH3HxPsCLuZYjDqFAM/C88ulg=\ngithub.com/docker/go-plugins-helpers v0.0.0-20240701071450-45e2431495c8/go.mod h1:LFyLie6XcDbyKGeVK6bHe+9aJTYCxWLBg5IrJZOaXKA=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\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/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=\ngithub.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=\ngithub.com/telepresenceio/clog v0.0.0-20260114221933-287514cf9831 h1:STun1hGf7oDZqRChINrQekn2EfypqhB8z67WlCZGHoU=\ngithub.com/telepresenceio/clog v0.0.0-20260114221933-287514cf9831/go.mod h1:UNeuJUkHiI0y2x59WmgSNMTTCYLMU12U5AUHhFi3BOA=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\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/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=\ngoogle.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\n"
  },
  {
    "path": "cmd/teleroute/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"syscall\"\n\n\t\"github.com/docker/go-plugins-helpers/network\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/handler\"\n\t\"github.com/telepresenceio/telepresence/cmd/teleroute/driver\"\n)\n\nconst (\n\tpluginSocket = \"/run/docker/plugins/teleroute.sock\"\n\tpluginLog    = \"/var/log/teleroute.log\"\n)\n\nfunc main() {\n\tlf, err := os.Create(pluginLog)\n\tif err != nil {\n\t\t_, _ = fmt.Fprintf(os.Stderr, \"Failed to create plugin log file %s: %s\\n\", pluginLog, err)\n\t\tos.Exit(1)\n\t}\n\tdefer lf.Close()\n\n\tlevel := clog.LevelInfo\n\tif debug, ok := os.LookupEnv(\"DEBUG\"); ok {\n\t\tif ok, _ = strconv.ParseBool(debug); ok {\n\t\t\tlevel = clog.LevelDebug\n\t\t}\n\t}\n\tctx := clog.WithLogger(context.Background(), slog.New(handler.NewText(handler.Output(lf), handler.TimeFormat(\"15:04:05.0000\"), handler.EnabledLevel(level))))\n\tpid, err := getPluginHostPID(ctx)\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\tos.Exit(1)\n\t}\n\tsigs := make(chan os.Signal, 1)\n\tsignal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)\n\tctx, cancel := context.WithCancel(ctx)\n\tgo func() {\n\t\t<-sigs\n\t\tclog.Info(ctx, \"Received shutdown signal\")\n\t\tcancel()\n\t}()\n\tif err := network.NewHandler(driver.New(ctx, pid)).ServeUnix(pluginSocket, 0); err != nil {\n\t\tclog.Error(ctx, err)\n\t\tos.Exit(1)\n\t}\n}\n\nconst pluginEntryPoint = \"/bin/docker-network-teleroute\"\n\nfunc getPluginHostPID(ctx context.Context) (int, error) {\n\tpls, err := filepath.Glob(\"/proc/*/exe\")\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"error listing entries matching glob /proc/*/exe: %v\", err)\n\t}\n\tfor _, pl := range pls {\n\t\tlt, _ := os.Readlink(pl)\n\t\tif lt == pluginEntryPoint {\n\t\t\tclog.Debugf(ctx, \"%s links to %s\", pl, pluginEntryPoint)\n\t\t\tif pid, err := strconv.Atoi(pl[6 : len(pl)-4]); err == nil {\n\t\t\t\tclog.Debugf(ctx, \"pid of %s is %d\", pluginEntryPoint, pid)\n\t\t\t\treturn pid, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn 0, fmt.Errorf(\"unable to find PID for %s\", pluginEntryPoint)\n}\n"
  },
  {
    "path": "docs/CONTRIBUTING.md",
    "content": "# Telepresence Documentation\n\nThis folder contains the Telepresence documentation in a format suitable for a versioned folder in the\ntelepresenceio/telepresence.io repository. The folder will show up in that repository when a new minor revision\ntag is created here.\n\nAssuming that a 2.20.0 release is pending, and that a release/v2.20.0 branch has been created, then:\n```console\n$ export TELEPRESENCE_VERSION=v2.20.0\n$ make prepare-release\n$ git push origin {,rpc/}v2.20.0 release/v2.20.0\n```\n\nwill result in a `docs/v2.20` folder with this folder's contents in the telepresenceio/telepresence.io repository.\n\nSubsequent bugfix tags for the same minor tag, i.e.:\n```console\n$ export TELEPRESENCE_VERSION=v2.20.1\n$ make prepare-release\n$ git push origin {,rpc/}v2.20.1 release/v2.20.1\n```\nwill not result in a new folder when it is pushed, but it will update the content of the `docs/v2.20` folder to\nreflect this folder's content for that tag.\n"
  },
  {
    "path": "docs/README.md",
    "content": "---\ndescription: Main menu when using plain markdown. Excluded when generating the website\n---\n# <img src=\"images/logo.png\" height=\"64px\"/> Telepresence Documentation\nraw markdown version, more bells and whistles at [telepresence.io](https://telepresence.io)\n\n- [Quick start](quick-start.md)\n- Install Telepresence\n  - [Install Client](install/client.md)\n  - [Upgrade Client](install/upgrade.md)\n  - [Install Traffic Manager](install/manager.md)\n  - [Cloud Provider Prerequisites](install/cloud.md)\n- Core concepts\n  - [The developer experience and the inner dev loop](concepts/devloop.md)\n  - [Making the remote local: Faster feedback, collaboration and debugging](concepts/faster.md)\n  - [Intercepts](concepts/intercepts.md)\n- How do I...\n  - [Code and debug an application locally](howtos/engage.md)\n  - [Use Telepresence with Docker](howtos/docker.md)\n  - [Extend Docker Compose with Telepresence](howtos/docker-compose.md)\n  - [Work with large clusters](howtos/large-clusters.md)\n  - [Host a cluster in Docker or a VM](howtos/cluster-in-vm.md)\n  - [Intercept TLS/mTLS Applications](howtos/mtls.md)\n  - [Use Telepresence with Azure (Microsoft Learn)](https://learn.microsoft.com/en-us/azure/aks/use-telepresence-aks.md)\n- Technical reference\n  - [Architecture](reference/architecture.md)\n  - [Telepresence CLI](reference/cli/telepresence.md)\n    - [telepresence completion](reference/cli/telepresence_completion.md)\n    - [telepresence compose](reference/cli/telepresence_compose.md)\n      - [attach](reference/cli/telepresence_compose_attach.md)\n      - [bridge](reference/cli/telepresence_compose_bridge.md)\n      - [build](reference/cli/telepresence_compose_build.md)\n      - [commit](reference/cli/telepresence_compose_commit.md)\n      - [config](reference/cli/telepresence_compose_config.md)\n      - [cp](reference/cli/telepresence_compose_cp.md)\n      - [create](reference/cli/telepresence_compose_create.md)\n      - [down](reference/cli/telepresence_compose_down.md)\n      - [events](reference/cli/telepresence_compose_events.md)\n      - [exec](reference/cli/telepresence_compose_exec.md)\n      - [export](reference/cli/telepresence_compose_export.md)\n      - [images](reference/cli/telepresence_compose_images.md)\n      - [kill](reference/cli/telepresence_compose_kill.md)\n      - [logs](reference/cli/telepresence_compose_logs.md)\n      - [ls](reference/cli/telepresence_compose_ls.md)\n      - [pause](reference/cli/telepresence_compose_pause.md)\n      - [port](reference/cli/telepresence_compose_port.md)\n      - [ps](reference/cli/telepresence_compose_ps.md)\n      - [publish](reference/cli/telepresence_compose_publish.md)\n      - [pull](reference/cli/telepresence_compose_pull.md)\n      - [push](reference/cli/telepresence_compose_push.md)\n      - [restart](reference/cli/telepresence_compose_restart.md)\n      - [rm](reference/cli/telepresence_compose_rm.md)\n      - [run](reference/cli/telepresence_compose_run.md)\n      - [scale](reference/cli/telepresence_compose_scale.md)\n      - [start](reference/cli/telepresence_compose_start.md)\n      - [stats](reference/cli/telepresence_compose_stats.md)\n      - [stop](reference/cli/telepresence_compose_stop.md)\n      - [top](reference/cli/telepresence_compose_top.md)\n      - [unpause](reference/cli/telepresence_compose_unpause.md)\n      - [up](reference/cli/telepresence_compose_up.md)\n      - [version](reference/cli/telepresence_compose_version.md)\n      - [volumes](reference/cli/telepresence_compose_volumes.md)\n      - [wait](reference/cli/telepresence_compose_wait.md)\n      - [watch](reference/cli/telepresence_compose_watch.md)\n    - [telepresence config](reference/cli/telepresence_config.md)\n      - [view](reference/cli/telepresence_config_view.md)\n    - [telepresence connect](reference/cli/telepresence_connect.md)\n    - [telepresence curl](reference/cli/telepresence_curl.md)\n    - [telepresence docker-run](reference/cli/telepresence_docker-run.md)\n    - [telepresence gather-logs](reference/cli/telepresence_gather-logs.md)\n    - [telepresence genyaml](reference/cli/telepresence_genyaml.md)\n      - [annotations](reference/cli/telepresence_genyaml_annotations.md)\n      - [annotations](reference/cli/telepresence_genyaml_annotations.md)\n      - [config](reference/cli/telepresence_genyaml_config.md)\n      - [container](reference/cli/telepresence_genyaml_container.md)\n      - [initcontainer](reference/cli/telepresence_genyaml_initcontainer.md)\n      - [volume](reference/cli/telepresence_genyaml_volume.md)\n    - [telepresence helm](reference/cli/telepresence_helm.md)\n      - [helm install](reference/cli/telepresence_helm_install.md)\n      - [helm lint](reference/cli/telepresence_helm_lint.md)\n      - [helm uninstall](reference/cli/telepresence_helm_uninstall.md)\n      - [helm upgrade](reference/cli/telepresence_helm_upgrade.md)\n      - [helm version](reference/cli/telepresence_helm_version.md)\n    - [telepresence ingest](reference/cli/telepresence_ingest.md)\n    - [telepresence intercept](reference/cli/telepresence_intercept.md)\n    - [telepresence leave](reference/cli/telepresence_leave.md)\n    - [telepresence list](reference/cli/telepresence_list.md)\n    - [telepresence list-contexts](reference/cli/telepresence_list-contexts.md)\n    - [telepresence list-namespaces](reference/cli/telepresence_list-namespaces.md)\n    - [telepresence loglevel](reference/cli/telepresence_loglevel.md)\n    - [telepresence mcp](reference/cli/telepresence_mcp.md)\n      - [start](reference/cli/telepresence_mcp_start.md)\n      - [tools](reference/cli/telepresence_mcp_tools.md)\n      - [stream](reference/cli/telepresence_mcp_stream.md)\n      - [claude](reference/cli/telepresence_mcp_claude.md)\n        - [enable](reference/cli/telepresence_mcp_claude_enable.md)\n        - [disable](reference/cli/telepresence_mcp_claude_disable.md)\n        - [list](reference/cli/telepresence_mcp_claude_list.md)\n      - [cursor](reference/cli/telepresence_mcp_cursor.md)\n        - [enable](reference/cli/telepresence_mcp_cursor_enable.md)\n        - [disable](reference/cli/telepresence_mcp_cursor_disable.md)\n        - [list](reference/cli/telepresence_mcp_cursor_list.md)\n      - [vscode](reference/cli/telepresence_mcp_vscode.md)\n        - [enable](reference/cli/telepresence_mcp_vscode_enable.md)\n        - [disable](reference/cli/telepresence_mcp_vscode_disable.md)\n        - [list](reference/cli/telepresence_mcp_vscode_list.md)\n    - [telepresence quit](reference/cli/telepresence_quit.md)\n    - [telepresence replace](reference/cli/telepresence_replace.md)\n    - [telepresence revoke](reference/cli/telepresence_revoke.md)\n    - [telepresence serve](reference/cli/telepresence_serve.md)\n    - [telepresence status](reference/cli/telepresence_status.md)\n    - [telepresence uninstall](reference/cli/telepresence_uninstall.md)\n    - [telepresence version](reference/cli/telepresence_version.md)\n    - [telepresence wiretap](reference/cli/telepresence_wiretap.md)\n  - [Laptop-side configuration](reference/config.md)\n  - [Cluster-side configuration](reference/cluster-config.md)\n  - [Using Docker for engagements](reference/docker-run.md)\n  - [Telepresence Compose Extensions](reference/compose.md)\n  - [Running Telepresence in a Docker container](reference/inside-container.md)\n  - [Environment variables](reference/environment.md)\n  - Engagements\n    - [Configure intercept using CLI](reference/engagements/cli.md)\n    - [Traffic Agent Sidecar](reference/engagements/sidecar.md)\n    - [Target a specific container](reference/engagements/container.md)\n    - [Dealing With Conflicting Engagements](reference/engagements/conflicts.md)\n  - [Telepresence Docker Plugins](reference/plugins.md)\n  - [Volume mounts](reference/volume.md)\n  - [RESTful API service](reference/restapi.md)\n  - [DNS resolution](reference/dns.md)\n  - [RBAC](reference/rbac.md)\n  - [Telepresence and VPNs](reference/vpn.md)\n  - [Networking through Virtual Network Interface](reference/tun-device.md)\n  - [Connection Routing](reference/routing.md)\n  - [Route Controller](reference/route-controller.md)\n  - [Monitoring](reference/monitoring.md)\n- Comparisons\n  - [Telepresence vs mirrord](compare/mirrord.md)\n- [FAQs](faqs.md)\n- [Troubleshooting](troubleshooting.md)\n- [Community](community.md)\n- [Release Notes](release-notes.md)\n- [Licenses](licenses.md)\n"
  },
  {
    "path": "docs/common/quantity.md",
    "content": "---\ntitle: Quantity\n---\nQuantity is measured in bytes. You can express it as a plain integer or as a fixed-point number using E, G, M, or K. You can also use the power-of-two equivalents: Gi, Mi, Ki. For example, the following represent roughly the same value:\n```\n128974848, 129e6, 129M, 123Mi\n```"
  },
  {
    "path": "docs/community.md",
    "content": "---\ntitle: Community\nhide_table_of_contents: true\n---\n\n# Community\n\n## Contributor's guide\nPlease review our [contributor's guide](https://github.com/telepresenceio/telepresence/blob/release/v2/CONTRIBUTING.md)\non GitHub to learn how you can help make Telepresence better.\n\n## Meetings\nCheck out our community [meeting schedule](https://github.com/telepresenceio/telepresence/blob/release/v2/MEETING_SCHEDULE.md) for opportunities to interact with Telepresence developers.\n"
  },
  {
    "path": "docs/compare/mirrord.md",
    "content": "---\ntitle: \"Telepresence vs mirrord\"\nhide_table_of_contents: true\n---\n\n## Telepresence\n\nTelepresence is a very feature rich tool, designed to handle a large majority of use-cases. You can use it as a cluster VPN only, or use one of its three different ways (replace, intercept, or ingest) to engage with the cluster's resources.\n\nTelepresence is intended to be installed in the cluster by an administrator and then let clients connect with a very limited set of permissions. This model is generally required by larger companies.\n\nThe client can be either completely contained in Docker or run directly on the workstation. The latter requires the creation of a virtual network device, but admin access is not needed when Telepresence is installed using a package installer that configures the root daemon as a system service.\n\n## mirrord\n\nMirrord was designed with simplicity in mind. You install the CLI tool, and that's it. It will do the rest automatically under the hood.\n\nMirrord solves the same problem as Telepresence, but in a different way. Instead of providing a proper network\ndevice and remotely mounted filesystems, mirrord will link the client application with a `mirrord-layer` shared library. This library will inject code that intercepts accesses to the network, file system, and environment variables, and reroute them to a corresponding process in the cluster (the `mirrord-agent`) which then interacts with the targeted pod.\n\n### Limitations with Code Injection\n\nTelepresence 1.x used the [code injection approach](https://www.getambassador.io/blog/code-injection-on-linux-and-macos), but desided to abandon it due to several limitations:\n\n1. It will only work on Linux and macOS platforms. There's no native support on Windows.\n2. It will only work with dynamically linked executables.\n3. It cannot be used with docker unless you rebuild the container and inject the `mirrord-layer` into it.\n4. `DYLD_INSERT_LIBRARIES` causes various problems on macOS (SIP prevents it from being used), especially on silicon-based machines where mirrord will require Rosetta.\n5. Should Apple decide to protect their intel-based platform the same way as the silicon-based one in a future release, then mirrord will likely be problematic to use on that platform.\n\n### Cluster Permissions\n\nMirrord does not require a sidecar. Instead they install a the `mirror-agent` into the namespace of the pod that it impersonates. That agent requires several permissions that a cluster admin might consider a security risk:\n\n* `CAP_NET_ADMIN` and `CAP_NET_RAW` - required for modifying routing tables\n* `CAP_SYS_PTRACE` - required for reading target pod environment\n* `CAP_SYS_ADMIN` - required for joining target pod network namespace\n\nUnless using \"mirrord for Teams\" (proprietary), all users must have permissions to create the job running the `mirror-agent` in the cluster.\n\n## Comparison Telepresence vs mirrord\n\nThis comparison chart applies to the Open Source editions of both products.\n\n| Feature                                                                      | Telepresence | mirrord |\n|------------------------------------------------------------------------------|--------------|---------|\n| Run or Debug your cluster containers locally                                 | ✅            | ✅       |\n| Does not need administrative permission on workstation                       | ✅ [^1]       | ✅       |\n| Can be used with very large clusters                                         | ✅            | ✅       |\n| Works without interrupting the remote service                                | ✅ [^2]       | ✅       |\n| Doesn't require injection of a sidecar                                       | ✅ [^3]       | ✅       |\n| Supports connecting to clusters over a corporate VPN                         | ✅            | ✅       |\n| Can intercept traffic                                                        | ✅            | ✅       |\n| Can filter traffic based on HTTP headers and paths                           | ✅            | ✅       |\n| Can mirror traffic                                                           | ✅            | ✅       |\n| Can ingest a container                                                       | ✅            | ❌       |\n| Can replace a container                                                      | ✅            | ❌       |\n| Can act as a cluster VPN only                                                | ✅            | ❌       |\n| Will work with statically linked binaries                                    | ✅            | ❌       |\n| Runs natively on windows                                                     | ✅            | ❌       |\n| Can intercept traffic to and from pod's localhost                            | ✅            | ❌       |\n| Remotely mounted file system available from all applications                 | ✅            | ❌       |\n| Cluster network available to all applications (including browser)            | ✅            | ❌       |\n| Can run the same docker container locally without rebuilding it              | ✅            | ❌       |\n| Integrates with Docker Compose                                               | ✅            | ❌       |\n| Provides an API server allowing introspection of replacements and intercepts | ✅            | ❌       |\n| Provides remote mounts as volumes in docker                                  | ✅            | ❌       |\n| Does not require special capabilities such as CAP_SYS_ADMIN in the cluster   | ✅            | ❌       |\n| Centralized client configuration using Helm chart                            | ✅            | ❌       |\n| Installed using a JSON-schema validated Helm chart                           | ✅            | ❌       |\n| Client need no special RBAC permissions                                      | ✅            | ❌       |\n\n[^1]: Telepresence does not require root access on the workstation when installed using a package installer (which configures the root daemon as a system service) or when running in docker mode.\n\n[^2]: The remote service will only restart when a traffic-agent sidecar is installed. Pod disruption budgets or pre-installed agents can be used to avoid interruptions.\n\n[^3]: A traffic-agent is necessary when engaging with a pod. It is unnecessary when using Telepresence as a VPN.\n"
  },
  {
    "path": "docs/concepts/devloop.md",
    "content": "---\ntitle: The developer experience and the inner dev loop\nhide_table_of_contents: true\n---\n\n# The developer experience and the inner dev loop\n\n## How is the developer experience changing?\n\nThe developer experience is the workflow a developer uses to develop, test, deploy, and release software.\n\nTypically this experience has consisted of both an inner dev loop and an outer dev loop. The inner dev loop is where the individual developer codes and tests, and once the developer pushes their code to version control, the outer dev loop is triggered.\n\nThe outer dev loop is _everything else_ that happens leading up to release. This includes code merge, automated code review, test execution, deployment, controlled (canary) release, and observation of results. The modern outer dev loop might include, for example, an automated CI/CD pipeline as part of a GitOps workflow and a progressive delivery strategy relying on automated canaries, i.e. to make the outer loop as fast, efficient and automated as possible.\n\nCloud-native technologies have fundamentally altered the developer experience in two ways: one, developers now have to take extra steps in the inner dev loop; two, developers need to be concerned with the outer dev loop as part of their workflow, even if most of their time is spent in the inner dev loop.\n\nEngineers now must design and build distributed service-based applications _and_ also assume responsibility for the full development life cycle. The new developer experience means that developers can no longer rely on monolithic application developer best practices, such as checking out the entire codebase and coding locally with a rapid “live-reload” inner development loop. Now developers have to manage external dependencies, build containers, and implement orchestration configuration (e.g. Kubernetes YAML). This may appear trivial at first glance, but this adds development time to the equation.\n\n## What is the inner dev loop?\n\nThe inner dev loop is the single developer workflow. A single developer should be able to set up and use an inner dev loop to code and test changes quickly.\n\nEven within the Kubernetes space, developers will find much of the inner dev loop familiar. That is, code can still be written locally at a level that a developer controls and committed to version control.\n\nIn a traditional inner dev loop, if a typical developer codes for 360 minutes (6 hours) a day, with a traditional local iterative development loop of 5 minutes — 3 coding, 1 building, i.e. compiling/deploying/reloading, 1 testing inspecting, and 10-20 seconds for committing code — they can expect to make ~70 iterations of their code per day. Any one of these iterations could be a release candidate. The only “developer tax” being paid here is for the commit process, which is negligible.\n\n![traditional inner dev loop](../images/trad-inner-dev-loop.png#devloop)\n\n## In search of lost time: How does containerization change the inner dev loop?\n\nThe inner dev loop is where writing and testing code happens, and time is critical for maximum developer productivity and getting features in front of end users. The faster the feedback loop, the faster developers can refactor and test again.\n\nChanges to the inner dev loop process, i.e., containerization, threaten to slow this development workflow down. Coding stays the same in the new inner dev loop, but code has to be containerized. The _containerized_ inner dev loop requires a number of new steps:\n\n* packaging code in containers\n* writing a manifest to specify how Kubernetes should run the application (e.g., YAML-based configuration information, such as how much memory should be given to a container)\n* pushing the container to the registry\n* deploying containers in Kubernetes\n\nEach new step within the container inner dev loop adds to overall development time, and developers are repeating this process frequently. If the build time is incremented to 5 minutes — not atypical with a standard container build, registry upload, and deploy — then the number of possible development iterations per day drops to ~40. At the extreme that’s a 40% decrease in potential new features being released. This new container build step is a hidden tax, which is quite expensive.\n\n\n![container inner dev loop](../images/container-inner-dev-loop.png#devloop)\n\n## Tackling the slow inner dev loop\n\nA slow inner dev loop can negatively impact frontend and backend teams, delaying work on individual and team levels and slowing releases into production overall.\n\nFor example:\n\n* Frontend developers have to wait for previews of backend changes on a shared dev/staging environment (for example, until CI/CD deploys a new version) and/or rely on mocks/stubs/virtual services when coding their application locally. These changes are only verifiable by going through the CI/CD process to build and deploy within a target environment.\n* Backend developers have to wait for CI/CD to build and deploy their app to a target environment to verify that their code works correctly with cluster or cloud-based dependencies as well as to share their work to get feedback.\n\nNew technologies and tools can facilitate cloud-native, containerized development. And in the case of a sluggish inner dev loop, developers can accelerate productivity with tools that help speed the loop up again.\n"
  },
  {
    "path": "docs/concepts/faster.md",
    "content": "---\ntitle: \"Making the remote local: Faster feedback, collaboration and debugging\"\nhide_table_of_contents: true\n---\n\n---\n# Making the remote local: Faster feedback, collaboration and debugging\n\nWith the goal of achieving fast, efficient development, developers need a set of approaches to bridge the gap between remote Kubernetes clusters and local development, and reduce time to feedback and debugging.\n\n## How should I set up a Kubernetes development environment?\n\nSetting up a development environment for Kubernetes can be much more complex than the setup for traditional web applications. Creating and maintaining a Kubernetes development environment relies on a number of external dependencies, such as databases or authentication.\n\nWhile there are several ways to set up a Kubernetes development environment, most introduce complexities and impediments to speed. The dev environment should be set up to easily code and test in conditions where a service can access the resources it depends on.\n\nA good way to meet the goals of faster feedback, possibilities for collaboration, and scale in a realistic production environment is the \"single service local, all other remote\" environment. Developing in a fully remote environment offers some benefits, but for developers, it offers the slowest possible feedback loop. With local development in a remote environment, the developer retains considerable control while using tools like [Telepresence](../quick-start.md) to facilitate fast feedback, debugging and collaboration.\n\n## What is Telepresence?\n\nTelepresence is an open source tool that lets developers [code and test microservices locally against a remote Kubernetes cluster](../quick-start.md). Telepresence facilitates more efficient development workflows while relieving the need to worry about other service dependencies.\n\n## How can I get fast, efficient local development?\n\nThe dev loop can be jump-started with the right development environment and Kubernetes development tools to support speed, efficiency and collaboration. Telepresence is designed to let Kubernetes developers code as though their laptop is in their Kubernetes cluster, enabling the service to run locally and be proxied into the remote cluster. Telepresence runs code locally and forwards requests to and from the remote Kubernetes cluster, bypassing the much slower process of waiting for a container to build, pushing it to registry, and deploying to production.\n\nA rapid and continuous feedback loop is essential for productivity and speed; Telepresence enables the fast, efficient feedback loop to ensure that developers can access the rapid local development loop they rely on without disrupting their own or other developers' workflows. Telepresence safely intercepts traffic from the production cluster and enables near-instant testing of code and local debugging in production.\n\nTelepresence works by deploying a two-way network proxy in a pod running in a Kubernetes cluster. This pod proxies data from the Kubernetes environment (e.g., TCP/UDP connections, environment variables, volumes) to the local process. This proxy can intercept traffic meant for the service and reroute it to a local copy, which is ready for further (local) development.\n"
  },
  {
    "path": "docs/concepts/intercepts.md",
    "content": "---\ntitle: \"Intercepts\"\ndescription: \"Short demonstration of global intercepts\"\nhide_table_of_contents: true\n---\n\nimport Admonition from '@theme/Admonition';\nimport Paper from '@mui/material/Paper';\nimport Tab from '@mui/material/Tab';\nimport TabContext from '@mui/lab/TabContext';\nimport TabList from '@mui/lab/TabList';\nimport TabPanel from '@mui/lab/TabPanel';\nimport TabsContainer from '@site/src/components/TabsContainer';\nimport Animation from '@site/src/components/InterceptAnimation';\n\n<TabsContainer>\n<TabPanel className=\"TabBody\" value=\"regular\">\n\n# No intercept\n\n<Paper className=\"interceptTab\">\n<Animation className=\"mode-regular\" />\n\nThis is the normal operation of your cluster without Telepresence.\n\n</Paper>\n</TabPanel>\n<TabPanel className=\"TabBody\" value=\"global\">\n\n<Paper className=\"interceptTab\">\n\n# Intercept\n\n<Animation className=\"mode-global\" />\n\n**Intercepts** replace the Kubernetes \"Orders\" service with the\nOrders service running on your laptop.  The users see no change, but\nwith all the traffic coming to your laptop, you can observe and debug\nwith all your dev tools.\n\n### Creating and using intercepts\n\n 1. Creating the intercept: Intercept your service from your CLI:\n\n    ```shell\n    telepresence intercept SERVICENAME\n    ```\n\n    <Admonition className=\"alert\" type=\"info\">\n\n    Make sure your current kubectl context points to the target\n    cluster.  If your service is running in a different namespace than\n    your current active context, use or change the `--namespace` flag.\n\n    </Admonition>\n\n 2. Using the intercept: Send requests to your service:\n\n    All requests will be sent to the version of your service that is\n    running in the local development environment.\n\n</Paper>\n</TabPanel>\n</TabsContainer>\n"
  },
  {
    "path": "docs/doc-links.yml",
    "content": "- title: Quick start\n  link: quick-start\n- title: Install Telepresence\n  items:\n    - title: Install Client\n      link: install/client\n    - title: Upgrade Client\n      link: install/upgrade\n    - title: Install Traffic Manager\n      link: install/manager\n    - title: Cloud Provider Prerequisites\n      link: install/cloud\n- title: Core concepts\n  items:\n    - title: The developer experience and the inner dev loop\n      link: concepts/devloop\n    - title: \"Making the remote local: Faster feedback, collaboration and debugging\"\n      link: concepts/faster\n    - title: Intercepts\n      link: concepts/intercepts\n- title: How do I...\n  items:\n    - title: Code and debug an application locally\n      link: howtos/engage\n    - title: Use Telepresence with Docker\n      link: howtos/docker\n    - title: Extend Docker Compose with Telepresence\n      link: howtos/docker-compose\n    - title: Work with large clusters\n      link: howtos/large-clusters\n    - title: Host a cluster in Docker or a VM\n      link: howtos/cluster-in-vm\n    - title: Intercept TLS/mTLS Applications\n      link: howtos/mtls\n    - title: Use Telepresence with Azure (Microsoft Learn)\n      link: https://learn.microsoft.com/en-us/azure/aks/use-telepresence-aks\n- title: Technical reference\n  items:\n    - title: Architecture\n      link: reference/architecture\n    - title: Telepresence CLI\n      link: reference/cli/telepresence\n      items:\n        - title: telepresence completion\n          link: reference/cli/telepresence_completion\n        - title: telepresence compose\n          link: reference/cli/telepresence_compose\n          items:\n            - title: attach\n              link: reference/cli/telepresence_compose_attach\n            - title: bridge\n              link: reference/cli/telepresence_compose_bridge\n            - title: build\n              link: reference/cli/telepresence_compose_build\n            - title: commit\n              link: reference/cli/telepresence_compose_commit\n            - title: config\n              link: reference/cli/telepresence_compose_config\n            - title: cp\n              link: reference/cli/telepresence_compose_cp\n            - title: create\n              link: reference/cli/telepresence_compose_create\n            - title: down\n              link: reference/cli/telepresence_compose_down\n            - title: events\n              link: reference/cli/telepresence_compose_events\n            - title: exec\n              link: reference/cli/telepresence_compose_exec\n            - title: export\n              link: reference/cli/telepresence_compose_export\n            - title: images\n              link: reference/cli/telepresence_compose_images\n            - title: kill\n              link: reference/cli/telepresence_compose_kill\n            - title: logs\n              link: reference/cli/telepresence_compose_logs\n            - title: ls\n              link: reference/cli/telepresence_compose_ls\n            - title: pause\n              link: reference/cli/telepresence_compose_pause\n            - title: port\n              link: reference/cli/telepresence_compose_port\n            - title: ps\n              link: reference/cli/telepresence_compose_ps\n            - title: publish\n              link: reference/cli/telepresence_compose_publish\n            - title: pull\n              link: reference/cli/telepresence_compose_pull\n            - title: push\n              link: reference/cli/telepresence_compose_push\n            - title: restart\n              link: reference/cli/telepresence_compose_restart\n            - title: rm\n              link: reference/cli/telepresence_compose_rm\n            - title: run\n              link: reference/cli/telepresence_compose_run\n            - title: scale\n              link: reference/cli/telepresence_compose_scale\n            - title: start\n              link: reference/cli/telepresence_compose_start\n            - title: stats\n              link: reference/cli/telepresence_compose_stats\n            - title: stop\n              link: reference/cli/telepresence_compose_stop\n            - title: top\n              link: reference/cli/telepresence_compose_top\n            - title: unpause\n              link: reference/cli/telepresence_compose_unpause\n            - title: up\n              link: reference/cli/telepresence_compose_up\n            - title: version\n              link: reference/cli/telepresence_compose_version\n            - title: volumes\n              link: reference/cli/telepresence_compose_volumes\n            - title: wait\n              link: reference/cli/telepresence_compose_wait\n            - title: watch\n              link: reference/cli/telepresence_compose_watch\n        - title: telepresence config\n          link: reference/cli/telepresence_config\n          items:\n            - title: view\n              link: reference/cli/telepresence_config_view\n        - title: telepresence connect\n          link: reference/cli/telepresence_connect\n        - title: telepresence curl\n          link: reference/cli/telepresence_curl\n        - title: telepresence docker-run\n          link: reference/cli/telepresence_docker-run\n        - title: telepresence gather-logs\n          link: reference/cli/telepresence_gather-logs\n        - title: telepresence genyaml\n          link: reference/cli/telepresence_genyaml\n          items:\n            - title: annotations\n              link: reference/cli/telepresence_genyaml_annotations\n            - title: annotations\n              link: reference/cli/telepresence_genyaml_annotations\n            - title: config\n              link: reference/cli/telepresence_genyaml_config\n            - title: container\n              link: reference/cli/telepresence_genyaml_container\n            - title: initcontainer\n              link: reference/cli/telepresence_genyaml_initcontainer\n            - title: volume\n              link: reference/cli/telepresence_genyaml_volume\n        - title: telepresence helm\n          link: reference/cli/telepresence_helm\n          items:\n            - title: helm install\n              link: reference/cli/telepresence_helm_install\n            - title: helm lint\n              link: reference/cli/telepresence_helm_lint\n            - title: helm uninstall\n              link: reference/cli/telepresence_helm_uninstall\n            - title: helm upgrade\n              link: reference/cli/telepresence_helm_upgrade\n            - title: helm version\n              link: reference/cli/telepresence_helm_version\n        - title: telepresence ingest\n          link: reference/cli/telepresence_ingest\n        - title: telepresence intercept\n          link: reference/cli/telepresence_intercept\n        - title: telepresence leave\n          link: reference/cli/telepresence_leave\n        - title: telepresence list\n          link: reference/cli/telepresence_list\n        - title: telepresence list-contexts\n          link: reference/cli/telepresence_list-contexts\n        - title: telepresence list-namespaces\n          link: reference/cli/telepresence_list-namespaces\n        - title: telepresence loglevel\n          link: reference/cli/telepresence_loglevel\n        - title: telepresence mcp\n          link: reference/cli/telepresence_mcp\n          items:\n            - title: start\n              link: reference/cli/telepresence_mcp_start\n            - title: tools\n              link: reference/cli/telepresence_mcp_tools\n            - title: stream\n              link: reference/cli/telepresence_mcp_stream\n            - title: claude\n              link: reference/cli/telepresence_mcp_claude\n              items:\n                - title: enable\n                  link: reference/cli/telepresence_mcp_claude_enable\n                - title: disable\n                  link: reference/cli/telepresence_mcp_claude_disable\n                - title: list\n                  link: reference/cli/telepresence_mcp_claude_list\n            - title: cursor\n              link: reference/cli/telepresence_mcp_cursor\n              items:\n                - title: enable\n                  link: reference/cli/telepresence_mcp_cursor_enable\n                - title: disable\n                  link: reference/cli/telepresence_mcp_cursor_disable\n                - title: list\n                  link: reference/cli/telepresence_mcp_cursor_list\n            - title: vscode\n              link: reference/cli/telepresence_mcp_vscode\n              items:\n                - title: enable\n                  link: reference/cli/telepresence_mcp_vscode_enable\n                - title: disable\n                  link: reference/cli/telepresence_mcp_vscode_disable\n                - title: list\n                  link: reference/cli/telepresence_mcp_vscode_list\n        - title: telepresence quit\n          link: reference/cli/telepresence_quit\n        - title: telepresence replace\n          link: reference/cli/telepresence_replace\n        - title: telepresence revoke\n          link: reference/cli/telepresence_revoke\n        - title: telepresence serve\n          link: reference/cli/telepresence_serve\n        - title: telepresence status\n          link: reference/cli/telepresence_status\n        - title: telepresence uninstall\n          link: reference/cli/telepresence_uninstall\n        - title: telepresence version\n          link: reference/cli/telepresence_version\n        - title: telepresence wiretap\n          link: reference/cli/telepresence_wiretap\n    - title: Laptop-side configuration\n      link: reference/config\n    - title: Cluster-side configuration\n      link: reference/cluster-config\n    - title: Using Docker for engagements\n      link: reference/docker-run\n    - title: Telepresence Compose Extensions\n      link: reference/compose\n    - title: Running Telepresence in a Docker container\n      link: reference/inside-container\n    - title: Environment variables\n      link: reference/environment\n    - title: Engagements\n      items:\n        - title: Configure intercept using CLI\n          link: reference/engagements/cli\n        - title: Traffic Agent Sidecar\n          link: reference/engagements/sidecar\n        - title: Target a specific container\n          link: reference/engagements/container\n        - title: Dealing With Conflicting Engagements\n          link: reference/engagements/conflicts\n    - title: Telepresence Docker Plugins\n      link: reference/plugins\n    - title: Volume mounts\n      link: reference/volume\n    - title: RESTful API service\n      link: reference/restapi\n    - title: DNS resolution\n      link: reference/dns\n    - title: RBAC\n      link: reference/rbac\n    - title: Telepresence and VPNs\n      link: reference/vpn\n    - title: Networking through Virtual Network Interface\n      link: reference/tun-device\n    - title: Connection Routing\n      link: reference/routing\n    - title: Route Controller\n      link: reference/route-controller\n    - title: Monitoring\n      link: reference/monitoring\n- title: Comparisons\n  items:\n    - title: Telepresence vs mirrord\n      link: compare/mirrord\n- title: FAQs\n  link: faqs\n- title: Troubleshooting\n  link: troubleshooting\n- title: Community\n  link: community\n- title: Release Notes\n  link: release-notes\n- title: Licenses\n  link: licenses\n"
  },
  {
    "path": "docs/faqs.md",
    "content": "---\ntitle: FAQs\ndescription: \"Learn how Telepresence helps with fast development and debugging in your Kubernetes cluster.\"\nhide_table_of_contents: true\n---\n\n# FAQs\n\n### Why Telepresence\n\nModern microservices-based applications that are deployed into Kubernetes often consist of tens or hundreds of services. The resource constraints and number of these services means that it is often difficult to impossible to run all of this on a local development machine, which makes fast development and debugging very challenging. The fast [inner development loop](concepts/devloop.md) from previous software projects is often a distant memory for cloud developers.\n\nTelepresence enables you to connect your local development machine seamlessly to the cluster via a two-way proxying mechanism. This enables you to code locally and run the majority of your services within a remote Kubernetes cluster — which in the cloud means you have access to effectively unlimited resources.\n\nUltimately, this empowers you to develop services locally and still test integrations with dependent services or data stores running in the remote cluster.\n\nTelepresence provides three different ways for you to code, debug, and test your service locally using your favourite local IDE and in-process debugger.\n\nFirst off, you can \"replace\" the service with your own local version. This means even though you run your service locally, you can see how it interacts with the rest of the services in the cluster. It's like swapping out a piece of a puzzle and seeing how the whole picture changes. Your local process will have access to the same network, environment, and volumes as the service that it replaces.\n\nYou can also \"intercept\" any requests made to a service. This is similar to replacing the service, but the remote service will keep running, perform background tasks, and handle traffic that isn't intercepted.\n\nFinally, you can \"ingest\" a service. Again, similar to a \"replace\", but nothing changes in the cluster during an \"ingest\", and no traffic is routed to the workstation.\n\n#### What operating systems does Telepresence work on?\n\nTelepresence currently works natively on macOS, Linux, and Windows.\n\n#### What architecture does Telepresence work on?\n\nAll Telepresence binaries are released for both AMD (Intel) and ARM (Apple Silicon) chips. \n\n#### What protocols can be intercepted by Telepresence?\n\nBoth TCP and UDP are supported.\n\n#### When using Telepresence to run a cluster service locally, are the Kubernetes cluster environment variables proxied on my local machine?\n\nYes, you can either set the container's environment variables on your machine or write the variables to a file to use with Docker or another build process. You can also directly pass the environments to a handler that runs locally. Please see [the environment variable reference doc](reference/environment.md) for more information.\n\n#### When using Telepresence to run a cluster service locally, can the associated container volume mounts also be mounted by my local machine?\n\nYes, and when running Docker, they can be used as docker volumes. Please see [the volume mounts reference doc](reference/volume.md) for more information.\n\n#### When connected to a Kubernetes cluster via Telepresence, can I access cluster-based services via their DNS name?\n\nYes. After you have successfully connected to your cluster via `telepresence connect -n <my_service_namespace>` you will be able to access any service in the connected namespace in your cluster via their DNS name.\n\nThis means you can curl endpoints directly e.g. `curl <my_service_name>:8080/mypath`.\n\nYou can also access services in other namespaces using their namespaced qualified name, e.g.`curl <my_service_name>.<my_other_namespace>:8080/mypath`.\n\nIn essence, Telepresence makes the DNS of the connected namespace available locally. This means that you can connect to all databases or middleware running in the cluster, such as MySQL, PostgreSQL and RabbitMQ, via their service name.\n\n#### When connected to a Kubernetes cluster via Telepresence, can I access cloud-based services and data stores via their DNS name?\n\nYou can connect to cloud-based data stores and services that are directly addressable within the cluster (e.g. when using an [ExternalName](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) Service type), such as AWS RDS, Google pub-sub, or Azure SQL Database.\n\n#### Will Telepresence be able to engage with workloads running on a private cluster or cluster running within a virtual private cloud (VPC)?\n\nYes, but it doesn't need to have a publicly accessible IP address.\n\nThe cluster must also have access to an external registry to be able to download the traffic-manager and traffic-agent images that are deployed when connecting with Telepresence.\n\n#### Why does running Telepresence sometimes require sudo access for the local daemon?\n\nThe local daemon needs to create a VIF (Virtual Network Interface) for outbound routing and DNS, which is a privileged operation. However, sudo is **not** required when:\n\n- Telepresence was installed using a [package installer](install/client.md) (`.pkg` on macOS, `.deb`/`.rpm` on Linux, or the Windows setup installer), which configures the root daemon as a system service.\n- Telepresence runs in [Docker mode](howtos/docker.md) (`telepresence connect --docker`).\n\nSudo is only needed when using a standalone binary installation without a system service.\n\n#### What components get installed in the cluster when running Telepresence?\n\nA `traffic-manager` service is deployed in a namespace of your choice (default 'ambassador') within your cluster, and this manages resilient intercepts and connections between your local machine and the cluster.\n\nA Traffic Agent container is injected per pod that is being engaged. The injection happens the first time a `replace`, an `ingest`, or an `intercept` is made on a workload, unless you choose to control the injection using an annotation, in which case the injection happens when the `traffic-manager` is installed.\n\n#### How can I remove all the Telepresence components installed within my cluster?\n\nYou can run the command `telepresence helm uninstall` to remove everything from the cluster, including the `traffic-manager`, and all the `traffic-agent` containers injected into each pod being engaged.\n\nYou also can run the command `telepresence uninstall <workload>` to remove the injected `traffic-agent` containers injected into each pod for that workload.\n\nAlso run `telepresence quit -s` to stop all local daemons running.\n\n#### What language is Telepresence written in?\n\nAll components of the Telepresence application and cluster components are written using Go. \n\n#### How does Telepresence connect and tunnel into the Kubernetes cluster?\n\nThe connection between your laptop and cluster is established by using\nthe `kubectl port-forward` machinery (though without actually spawning\na separate program) to establish a TLS encrypted connection to Telepresence\nTraffic Manager and Traffic Agents in the cluster, and running Telepresence's custom VPN\nprotocol over that connection.\n\n#### Is Telepresence OSS open source?\n\nYes, it is! You'll find both source code and documentation in the [Telepresence GitHub repository](https://github.com/telepresenceio/telepresence), licensed using the [apache License Version 2.0](https://github.com/telepresenceio/telepresence?tab=License-1-ov-file#readme).\n\n#### How do I share my feedback on Telepresence?\n\nYour feedback is always appreciated and helps us build a product that provides as much value as possible for our community. You can chat with us directly on our #telepresence-oss channel at the [CNCF Slack](https://slack.cncf.io), and also report issues or create pull-requests on the GitHub repository.\n"
  },
  {
    "path": "docs/helm/values.schema.json",
    "content": "{\"$defs\":{\"affinity\":{\"$ref\":\"#/definitions/io.k8s.api.core.v1.Affinity\"},\"duration\":{\"pattern\":\"^[+-]?(\\\\d+(\\\\.\\\\d+)?(h|m|s|ms|us|µs))+\",\"type\":\"string\"},\"labelSelector\":{\"$ref\":\"#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector\"},\"localObjectReference\":{\"$ref\":\"#/definitions/io.k8s.api.core.v1.LocalObjectReference\"},\"logLevel\":{\"enum\":[\"error\",\"warning\",\"warn\",\"info\",\"debug\",\"trace\"],\"type\":\"string\"},\"nodeSelector\":{\"additionalProperties\":{\"type\":\"string\"},\"type\":\"object\"},\"podSecurityContext\":{\"$ref\":\"#/definitions/io.k8s.api.core.v1.PodSecurityContext\"},\"probe\":{\"$ref\":\"#/definitions/io.k8s.api.core.v1.Probe\"},\"pullPolicy\":{\"enum\":[\"Always\",\"Never\",\"IfNotPresent\"],\"type\":\"string\"},\"quantity\":{\"$ref\":\"#/definitions/io.k8s.apimachinery.pkg.api.resource.Quantity\"},\"resourceRequirements\":{\"$ref\":\"#/definitions/io.k8s.api.core.v1.ResourceRequirements\"},\"rfc1123Label\":{\"description\":\"RFC 1123 label name\",\"pattern\":\"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$\",\"type\":\"string\"},\"securityContext\":{\"$ref\":\"#/definitions/io.k8s.api.core.v1.SecurityContext\"},\"subject\":{\"$ref\":\"#/definitions/io.k8s.api.rbac.v1.Subject\"},\"toleration\":{\"$ref\":\"#/definitions/io.k8s.api.core.v1.Toleration\"}},\"$id\":\"https://github.com/telepresenceio/telepresence-oss.schema.json\",\"$schema\":\"https://json-schema.org/draft/2020-12/schema#\",\"additionalProperties\":false,\"description\":\"Values Schema for the Telepresence OSS Helm Chart\",\"properties\":{\"affinity\":{\"$ref\":\"#/$defs/affinity\",\"description\":\"If specified, the pod's scheduling constraints\"},\"agent\":{\"additionalProperties\":false,\"description\":\"Telepresence traffic-agent configuration\",\"properties\":{\"enableConsumptionMetrics\":{\"description\":\"Enable (the default) or disable the consumption metrics of the traffic-agent. Only valid when the prometheus.port is set.\",\"type\":\"boolean\"},\"enableH2cProbing\":{\"description\":\"If enabled, the agent will probe for HTTP2 using clear-text (h2c) support non-TLS ports unless they are configured with appProtocol.\",\"type\":\"boolean\"},\"image\":{\"additionalProperties\":false,\"properties\":{\"name\":{\"description\":\"The name of the injected agent image\",\"type\":\"string\"},\"pullPolicy\":{\"$ref\":\"#/$defs/pullPolicy\",\"description\":\"Pull policy in the webhook for the traffic agent image\"},\"pullSecrets\":{\"description\":\"The Secret storing any credentials needed to access the image in a private registry\",\"items\":{\"$ref\":\"#/$defs/localObjectReference\"},\"type\":\"array\"},\"registry\":{\"description\":\"The registry for the injected agent image\",\"type\":\"string\"},\"tag\":{\"description\":\"Overrides the image tag whose default is the chart appVersion\",\"type\":\"string\"}},\"type\":\"object\"},\"initContainer\":{\"additionalProperties\":false,\"description\":\"Configuration for the init-container that runs before the traffic-agent\",\"properties\":{\"enabled\":{\"description\":\"Enable/Disable the init-container\",\"type\":\"boolean\"}},\"type\":\"object\"},\"initResources\":{\"$ref\":\"#/$defs/resourceRequirements\",\"description\":\"Resource requirements for the init-container\"},\"initSecurityContext\":{\"$ref\":\"#/$defs/securityContext\",\"description\":\"Security context for the init-container\"},\"logLevel\":{\"$ref\":\"#/$defs/logLevel\",\"description\":\"Loglevel for the agent, if different from the traffic-manager\"},\"maxIdleTime\":{\"$ref\":\"#/$defs/duration\",\"description\":\"maximum time the agent is idle (no engagements to the workload) before it is cleaned up by the traffic manager\"},\"mountPolicies\":{\"additionalProperties\":{\"description\":\"The name or path prefix match of the volume that the policy applies to\",\"enum\":[\"Remote\",\"RemoteReadOnly\",\"Local\",\"Ignore\"],\"type\":\"string\"},\"description\":\"List of volume mount policies. Defaults to {\\\"/tmp\\\" : \\\"Ignore\\\"}\",\"type\":\"object\"},\"port\":{\"description\":\"Port number of the first port that the agent will use\",\"type\":\"integer\"},\"resources\":{\"$ref\":\"#/$defs/resourceRequirements\",\"description\":\"Resource requirements for the agent\"},\"securityContext\":{\"$ref\":\"#/$defs/securityContext\",\"description\":\"Security context for the agent\"},\"watchRetryInterval\":{\"$ref\":\"#/$defs/duration\",\"description\":\"The interval between retries that a watcher uses when the gRPC connection to the traffic manager is lost\"}},\"type\":\"object\"},\"agentInjector\":{\"additionalProperties\":false,\"description\":\"Properties for the agent injector service\",\"properties\":{\"certificate\":{\"additionalProperties\":false,\"properties\":{\"accessMethod\":{\"description\":\"Method used by the agent injector to access the certificate (watch or mount)\",\"enum\":[\"watch\",\"mount\"],\"type\":\"string\"},\"certmanager\":{\"additionalProperties\":false,\"properties\":{\"commonName\":{\"description\":\"The common name of the generated Certmanager certificate\",\"type\":\"string\"},\"duration\":{\"$ref\":\"#/$defs/duration\",\"description\":\"The certificate validity duration\"},\"issuerRef\":{\"additionalProperties\":false,\"properties\":{\"kind\":{\"description\":\"The Issuer kind to use to generate the self signed certificate (Issuer of ClusterIssuer)\",\"enum\":[\"Issuer\",\"ClusterIssuer\"],\"type\":\"string\"},\"name\":{\"description\":\"The Issuer name to use to generate the self signed certificate\",\"type\":\"string\"}},\"type\":\"object\"}},\"type\":\"object\"},\"method\":{\"description\":\"Method used when generating the certificate used for mutating webhook (helm, supplied, or certmanager)\",\"enum\":[\"helm\",\"supplied\",\"certmanager\"],\"type\":\"string\"},\"regenerate\":{\"description\":\"Whether the certificate used for the mutating webhook should be regenerated\",\"type\":\"boolean\"}},\"type\":\"object\"},\"enabled\":{\"description\":\"Enable/Disable the agent-injector and its webhook\",\"type\":\"boolean\"},\"injectPolicy\":{\"description\":\"Determines when an agent is injected, possible values are OnDemand and WhenEnabled\",\"enum\":[\"OnDemand\",\"WhenEnabled\"],\"type\":\"string\"},\"mutationAware\":{\"description\":\"Include changes to the pod template that are contributed by other injectors. Implies reinvocationPolicy=IfNeeded\",\"type\":\"boolean\"},\"name\":{\"description\":\"Name to use with objects associated with the agent-injector\",\"type\":\"string\"},\"secret\":{\"properties\":{\"name\":{\"$ref\":\"#/$defs/rfc1123Label\",\"description\":\"The name of the secret the agent-injector webhook uses for authorization with the kubernetes api will expose\"}},\"type\":\"object\"},\"service\":{\"additionalProperties\":false,\"properties\":{\"type\":{\"description\":\"Type of service for the agent-injector.\",\"type\":\"string\"}},\"type\":\"object\"},\"webhook\":{\"additionalProperties\":false,\"properties\":{\"admissionReviewVersions\":{\"description\":\"List of supported admissionReviewVersions\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"failurePolicy\":{\"description\":\"Action to take on unexpected failure or timeout of webhook.\",\"enum\":[\"Fail\",\"Ignore\"],\"type\":\"string\"},\"name\":{\"description\":\"The name of the agent-injector webhook\",\"type\":\"string\"},\"port\":{\"description\":\"Port for the service that provides the admission webhook\",\"type\":\"integer\"},\"reinvocationPolicy\":{\"description\":\"Specify if the webhook may be called again after the initial webhook call. Possible values are Never and IfNeeded\",\"enum\":[\"IfNeeded\",\"Never\"],\"type\":\"string\"},\"servicePath\":{\"description\":\"Path to the service that provides the admission webhook\",\"type\":\"string\"},\"sideEffects\":{\"description\":\"Any side effects the admission webhook makes outside of AdmissionReview\",\"enum\":[\"None\",\"NoneOnDryRun\",\"Some\",\"Unknown\"],\"type\":\"string\"},\"timeoutSeconds\":{\"description\":\"Timeout of the admission webhook\",\"type\":\"integer\"}},\"type\":\"object\"}},\"type\":\"object\"},\"apiPort\":{\"description\":\"The port used by the Traffic Manager gRPC API\",\"type\":\"integer\"},\"client\":{\"properties\":{\"connectionTTL\":{\"$ref\":\"#/$defs/duration\",\"description\":\"Deprecated use grpc.connectionTTL\",\"type\":\"string\"},\"dns\":{\"properties\":{\"excludeSuffixes\":{\"description\":\"Suffixes for which the client DNS resolver will always fail (or fallback in case of the overriding resolver) Defaults to [\\\".com\\\", \\\".io\\\", \\\".net\\\", \\\".org\\\", \\\".ru\\\"]\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"includeSuffixes\":{\"description\":\"Suffixes for which the client DNS resolver will always attempt to do a lookup. Includes have higher priority than excludes\",\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"type\":\"object\"},\"routing\":{\"properties\":{\"allowConflictingSubnets\":{\"description\":\"Allow the specified subnets to be routed even if they conflict with other routes on the local machine\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"alsoProxySubnets\":{\"description\":\"The virtual network interface of connected clients will also proxy these subnets\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"neverProxySubnets\":{\"description\":\"The virtual network interface of connected clients never proxy these subnets\",\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"type\":\"object\"}},\"type\":\"object\"},\"clientRbac\":{\"additionalProperties\":false,\"properties\":{\"create\":{\"description\":\"Create RBAC resources for non-admin users with this release\",\"type\":\"boolean\"},\"namespaces\":{\"description\":\"The namespaces to give users access to. Defaults to Traffic Manager's namespaces (unless dynamic)\",\"items\":{\"$ref\":\"#/$defs/rfc1123Label\"},\"type\":\"array\"},\"ruleExtras\":{\"description\":\"If set, run the clientRbac-ruleExtras template to add additional RBAC rules.\",\"type\":\"boolean\"},\"subjects\":{\"description\":\"The user accounts to tie the created roles to\",\"items\":{\"$ref\":\"#/$defs/subject\"},\"type\":\"array\"}},\"type\":\"object\"},\"global\":{\"additionalProperties\":true,\"type\":\"object\"},\"grpc\":{\"additionalProperties\":false,\"properties\":{\"connectionTTL\":{\"$ref\":\"#/$defs/duration\",\"description\":\"The time that the traffic-manager or traffic-agent will retain a client connection without any sign of life from the workstation\",\"type\":\"string\"},\"maxReceiveSize\":{\"$ref\":\"#/$defs/quantity\",\"description\":\"maxReceiveSize is a quantity that configures the maximum message size that the traffic manager will service.\"}},\"type\":\"object\"},\"hooks\":{\"additionalProperties\":false,\"properties\":{\"busybox\":{\"additionalProperties\":false,\"properties\":{\"image\":{\"description\":\"The name of the image to use for busybox\",\"type\":\"string\"},\"imagePullSecrets\":{\"description\":\"The Secret storing any credentials needed to access the image in a private registry\",\"items\":{\"$ref\":\"#/$defs/localObjectReference\"},\"type\":\"array\"},\"pullPolicy\":{\"$ref\":\"#/$defs/pullPolicy\",\"description\":\"Pull policy in the webhook for the image\"},\"registry\":{\"description\":\"The registry to download the image from\",\"type\":\"string\"},\"tag\":{\"description\":\"Override the version of busybox to be installed\",\"type\":\"string\"}},\"type\":\"object\"},\"curl\":{\"additionalProperties\":false,\"properties\":{\"image\":{\"description\":\"The name of the image to use for curl\",\"type\":\"string\"},\"imagePullSecrets\":{\"description\":\"The Secret storing any credentials needed to access the image in a private registry\",\"items\":{\"$ref\":\"#/$defs/localObjectReference\"},\"type\":\"array\"},\"pullPolicy\":{\"$ref\":\"#/$defs/pullPolicy\",\"description\":\"Pull policy in the webhook for the image\"},\"registry\":{\"description\":\"The registry to download the image from\",\"type\":\"string\"},\"tag\":{\"description\":\"Override the version of curl to be installed\",\"type\":\"string\"}},\"type\":\"object\"},\"podSecurityContext\":{\"$ref\":\"#/$defs/podSecurityContext\",\"description\":\"The Kubernetes SecurityContext for the chart hooks Pod\"},\"resources\":{\"$ref\":\"#/$defs/resourceRequirements\",\"description\":\"Define resource requests and limits for the chart hooks\"},\"securityContext\":{\"$ref\":\"#/$defs/securityContext\",\"description\":\"The Kubernetes SecurityContext for the chart hooks Container\"}},\"type\":\"object\"},\"hostNetwork\":{\"description\":\"Sets the spec.template.spec.hostNetwork for the Traffic Manager. Set this to true when using Calico on AWS EKS to ensure that the mutating webhook can communicate with the traffic manager\",\"type\":\"boolean\"},\"image\":{\"additionalProperties\":false,\"properties\":{\"imagePullSecrets\":{\"description\":\"The Secret storing any credentials needed to access the image in a private registry\",\"items\":{\"$ref\":\"#/$defs/localObjectReference\"},\"type\":\"array\"},\"name\":{\"description\":\"The name of the image to use for the traffic-manager\",\"type\":\"string\"},\"pullPolicy\":{\"$ref\":\"#/$defs/pullPolicy\",\"description\":\"Pull policy in the webhook for the traffic-manager image\"},\"registry\":{\"description\":\"The registry to download the image from\",\"type\":\"string\"},\"tag\":{\"description\":\"Overrides the image tag whose default is the chart appVersion\",\"type\":\"string\"}},\"type\":\"object\"},\"intercept\":{\"additionalProperties\":false,\"properties\":{\"allowGlobalIntercepts\":{\"description\":\"Allow global TCP/UDP intercepts. When set to false, only HTTP intercepts with header or path filters are allowed. This prevents users from creating global intercepts that block other users from intercepting the same port.\",\"type\":\"boolean\"},\"environment\":{\"properties\":{\"excluded\":{\"description\":\"Environment variables to exclude from the list sent to the client during engagement\",\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"type\":\"object\"},\"inactiveBlockTimeout\":{\"$ref\":\"#/$defs/duration\",\"description\":\"The maximum amount of time an intercept may be held by a client that is unreachable or inactive. Once this timeout is exceeded, the intercept no longer blocks conflicting intercepts and may be automatically removed when another client attempts to create a conflicting intercept.\"}},\"type\":\"object\"},\"isCI\":{\"description\":\"isCI can be set to force the traffic-manager namespace to be \\\"ambassador\\\"\",\"type\":\"boolean\"},\"livenessProbe\":{\"$ref\":\"#/$defs/probe\",\"description\":\"Define livenessProbe for the traffic-manager\"},\"logLevel\":{\"$ref\":\"#/$defs/logLevel\",\"description\":\"Define the logging level of the Traffic Manager\"},\"managerRbac\":{\"additionalProperties\":false,\"properties\":{\"create\":{\"description\":\"Create RBAC resources for traffic-manager with this release\",\"type\":\"boolean\"},\"namespaces\":{\"deprecated\":true,\"description\":\"Declares a fixed set of managed namespaces\",\"items\":{\"$ref\":\"#/$defs/rfc1123Label\"},\"type\":\"array\"}},\"type\":\"object\"},\"maxNamespaceSpecificWatchers\":{\"description\":\"Threshold controlling when the traffic-manager switches from using watchers for each managed namespace to using cluster-wide watchers\",\"maximum\":50,\"minimum\":0,\"type\":\"integer\"},\"nameOverride\":{\"description\":\"Override the default name prefix used when creating RBAC resources\",\"type\":\"string\"},\"namespaceSelector\":{\"$ref\":\"#/$defs/labelSelector\",\"description\":\"Declares the managed namespace using matchLabels and matchExpressions. Mutually exclusive to namespaces\"},\"namespaces\":{\"description\":\"Declares a fixed set of managed namespaces. Mutually exclusive to namespaceSelector\",\"items\":{\"$ref\":\"#/$defs/rfc1123Label\"},\"type\":\"array\"},\"nodeSelector\":{\"$ref\":\"#/$defs/nodeSelector\",\"description\":\"Define which Nodes you want to the Traffic Manager to be deployed to.\"},\"podAnnotations\":{\"additionalProperties\":{\"type\":\"string\"},\"description\":\"Annotations for the Traffic Manager Pod\",\"type\":\"object\"},\"podCIDRStrategy\":{\"description\":\"Define the strategy that the traffic-manager uses to discover what CIDRs the cluster uses for pods\",\"enum\":[\"auto\",\"coverPodIPs\",\"environment\",\"nodePodCIDRs\"],\"type\":\"string\"},\"podCIDRs\":{\"description\":\"podCIDRs is the verbatim list of CIDRs used when the podCIDRStrategy is set to environment\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"podLabels\":{\"additionalProperties\":{\"type\":\"string\"},\"description\":\"Labels for the Traffic Manager Pod\",\"type\":\"object\"},\"podSecurityContext\":{\"$ref\":\"#/$defs/podSecurityContext\",\"description\":\"The Kubernetes SecurityContext for the traffic-manager Pod\"},\"priorityClassName\":{\"description\":\"Name of the existing pod priority class to be used\",\"type\":\"string\"},\"prometheus\":{\"additionalProperties\":false,\"properties\":{\"dropClientLabel\":{\"description\":\"optionally drop the client label from prometheus metrics for GDPR personal data compliance\",\"type\":\"boolean\"},\"port\":{\"description\":\"Set this port number to enable a prometheus metrics http server for the traffic manager\",\"type\":\"integer\"}},\"type\":\"object\"},\"rbac\":{\"additionalProperties\":false,\"properties\":{\"only\":{\"description\":\"Only create the RBAC resources and omit the traffic-manger\",\"type\":\"boolean\"}},\"type\":\"object\"},\"readinessProbe\":{\"$ref\":\"#/$defs/probe\",\"description\":\"Define readinessProbe for the Traffic Manger.\"},\"replicaCount\":{\"const\":1,\"description\":\"Number or replicas for the traffic-manager. The Traffic Manager only support running with one replica at the moment.\"},\"resources\":{\"$ref\":\"#/$defs/resourceRequirements\",\"description\":\"Define resource requests and limits for the Traffic Manger\"},\"routeController\":{\"additionalProperties\":false,\"description\":\"Configuration for the route-controller DaemonSet that installs blackhole routes for deleted service ClusterIPs to prevent routing loops on local clusters.\",\"properties\":{\"enabled\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"boolean\"}],\"description\":\"Enable or disable the route-controller DaemonSet. When null (the default), the route-controller is automatically enabled for local clusters, detected by image.registry being \\\"local\\\" or starting with \\\"localhost:\\\". Set to true to force-enable or false to force-disable regardless of registry.\"},\"image\":{\"additionalProperties\":false,\"properties\":{\"name\":{\"description\":\"The name of the route-controller image\",\"type\":\"string\"},\"pullPolicy\":{\"anyOf\":[{\"const\":\"\",\"type\":\"string\"},{\"$ref\":\"#/$defs/pullPolicy\"}],\"description\":\"Pull policy for the route-controller image. Empty string inherits from image.pullPolicy.\"},\"registry\":{\"description\":\"The registry for the route-controller image\",\"type\":\"string\"}},\"type\":\"object\"},\"logLevel\":{\"anyOf\":[{\"const\":\"\",\"type\":\"string\"},{\"$ref\":\"#/$defs/logLevel\"}],\"description\":\"Log level for the route-controller. Empty string inherits from logLevel.\"},\"serviceCIDRs\":{\"description\":\"Service CIDRs for which subnet-level blackhole routes are installed at startup. If empty, the controller queries the ServiceCIDR API (k8s \\u003e= 1.33) automatically. For older clusters, set this explicitly (e.g. [\\\"10.96.0.0/12\\\"]).\",\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"type\":\"object\"},\"schedulerName\":{\"description\":\"Specify a scheduler for Traffic Manager Pod and hooks Pod\",\"type\":\"string\"},\"securityContext\":{\"$ref\":\"#/$defs/securityContext\",\"description\":\"The Kubernetes SecurityContext for the traffic-manager container\"},\"service\":{\"additionalProperties\":false,\"properties\":{\"type\":{\"description\":\"The type of service for the traffic-manager\",\"type\":\"string\"}},\"type\":\"object\"},\"startupProbe\":{\"$ref\":\"#/$defs/probe\",\"description\":\"Define startupProbe for the Traffic Manger.\"},\"telepresenceAPI\":{\"additionalProperties\":false,\"properties\":{\"port\":{\"description\":\"The port on agent's localhost where the Telepresence API server can be found\",\"type\":\"integer\"}},\"type\":\"object\"},\"timeouts\":{\"additionalProperties\":false,\"properties\":{\"agentArrival\":{\"$ref\":\"#/$defs/duration\",\"description\":\"The time that the traffic-manager will wait for the traffic-agent to arrive\"}},\"type\":\"object\"},\"tolerations\":{\"description\":\"Define tolerations for the Traffic Manager to ignore Node taints\",\"items\":{\"$ref\":\"#/$defs/toleration\"},\"type\":\"array\"},\"workloads\":{\"additionalProperties\":false,\"properties\":{\"argoRollouts\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"description\":\"Enable/Disable the argo-rollouts integration\",\"type\":\"boolean\"}},\"type\":\"object\"},\"deployments\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"description\":\"Enable/Disable the support for Deployments\",\"type\":\"boolean\"}},\"type\":\"object\"},\"replicaSets\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"description\":\"Enable/Disable the support for ReplicaSets\",\"type\":\"boolean\"}},\"type\":\"object\"},\"statefulSets\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"description\":\"Enable/Disable the support for StatefulSets\",\"type\":\"boolean\"}},\"type\":\"object\"}},\"type\":\"object\"}},\"title\":\"Telepresence Values\",\"type\":\"object\"}"
  },
  {
    "path": "docs/howtos/cluster-in-vm.md",
    "content": "---\ntitle: Host a cluster in Docker or a VM\ndescription: Use Telepresence to engage with services in a cluster running in a hosted docker container or virtual\n machine.\nhide_table_of_contents: true\n---\n\n# Network considerations for locally hosted clusters\n\n## The problem\nTelepresence creates a Virtual Network Interface ([VIF](../reference/tun-device.md)) that maps the cluster subnets to the host machine when it connects. If you're running Kubernetes locally (e.g., Docker Desktop, Kind, Minikube, k3s), you may encounter network problems because the devices in the host are also accessible from the cluster's nodes.\n\n### Example:\nA k3s cluster runs in a headless VirtualBox machine that uses a \"host-only\" network. This network will allow both host-to-guest and guest-to-host connections. In other words, the cluster will have access to the host's network and, while Telepresence is connected, also to its VIF. This means that from the cluster's perspective, there will now be more than one interface that maps the cluster's subnets; the ones already present in the cluster's nodes, and then the Telepresence VIF, mapping them again.\n\nNow, if a request arrives to Telepresence covered by a subnet mapped by the VIF, the request is routed to the cluster. If the cluster for some reason doesn't find a corresponding listener that can handle the request, it will eventually try the host network, and find the VIF. The VIF routes the request to the cluster and now the recursion is in motion. The final outcome of the request will likely be a timeout but since the recursion is very resource intensive (a large amount of very rapid connection requests), this will likely also affect other connections in a bad way. \n\n## Solution\n\n### Prevent recursion in the VIF\nTo prevent recursive connections within the VIF, set the client configuration property `routing.recursionBlockDuration` to a short timeout value.\nA value of `1ms` is typically sufficient. This configuration will temporarily block new connections to a specific IP:PORT pair immediately after a\nconnection has been established, thereby preventing looped connections back into the VIF. The block remains in effect for the specified duration.\n\n### Create a bridge network\nAn alternative to using the `routing.recursionBlockDuration` can be to create a bridge network. It acts as a Link Layer (L2) device that forwards traffic between network segments. By creating a bridge network, you can bypass the host's network stack, and instead make the Kubernetes cluster to connect directly to the same router as your host.\n\nTo create a bridge network, you need to change the network settings of the guest running a cluster's node so that it connects directly to a physical network device on your host. The details on how to configure the bridge depend on what type of virtualization solution you're using.\n\n#### Vagrant + Virtualbox + k3s example\nHere's a sample `Vagrantfile` that will spin up a server node and two agent nodes in three headless instances using a bridged network. It also adds the configuration needed for the cluster to host a docker repository (very handy in case you want to save bandwidth). The Kubernetes registry manifest must be applied using `kubectl -f registry.yaml` once the cluster is up and running.\n\n##### Vagrantfile\n```ruby\n# -*- mode: ruby -*-\n# vi: set ft=ruby :\n\n# bridge is the name of the host's default network device\n$bridge = 'wlp5s0'\n\n# default_route should be the IP of the host's default route.\n$default_route = '192.168.1.1'\n\n# nameserver must be the IP of an external DNS, such as 8.8.8.8\n$nameserver = '8.8.8.8'\n\n# server_name should also be added to the host's /etc/hosts file and point to the server_ip\n# for easy access when pushing docker images\nserver_name = 'multi'\n\n# static IPs for the server and agents. Those IPs must be on the default router's subnet\nserver_ip = '192.168.1.110'\nagents = {\n  'agent1' => '192.168.1.111',\n  'agent2' => '192.168.1.112',\n}\n\n# Extra parameters in INSTALL_K3S_EXEC variable because of\n# K3s picking up the wrong interface when starting server and agent\n# https://github.com/alexellis/k3sup/issues/306\nserver_script = <<-SHELL\n    sudo -i\n    apk add curl\n    export INSTALL_K3S_EXEC=\"--bind-address=#{server_ip} --node-external-ip=#{server_ip} --flannel-iface=eth1\"\n    mkdir -p /etc/rancher/k3s\n    cat <<-'EOF' > /etc/rancher/k3s/registries.yaml\nmirrors:\n  \"multi:5000\":\n    endpoint:\n      - \"http://#{server_ip}:5000\"\nEOF\n    curl -sfL https://get.k3s.io | sh -\n    echo \"Sleeping for 5 seconds to wait for k3s to start\"\n    sleep 5\n    cp /var/lib/rancher/k3s/server/token /vagrant_shared\n    cp /etc/rancher/k3s/k3s.yaml /vagrant_shared\n    cp /etc/rancher/k3s/registries.yaml /vagrant_shared\n    SHELL\n\nagent_script = <<-SHELL\n    sudo -i\n    apk add curl\n    export K3S_TOKEN_FILE=/vagrant_shared/token\n    export K3S_URL=https://#{server_ip}:6443\n    export INSTALL_K3S_EXEC=\"--flannel-iface=eth1\"\n    mkdir -p /etc/rancher/k3s\n    cat <<-'EOF' > /etc/rancher/k3s/registries.yaml\nmirrors:\n  \"multi:5000\":\n    endpoint:\n      - \"http://#{server_ip}:5000\"\nEOF\n    curl -sfL https://get.k3s.io | sh -\n    SHELL\n\ndef config_vm(name, ip, script, vm)\n  # The network_script has two objectives:\n  # 1. Ensure that the guest's default route is the bridged network (bypass the network of the host)\n  # 2. Ensure that the DNS points to an external DNS service, as opposed to the DNS of the host that\n  #    the NAT network provides.\n  network_script = <<-SHELL\n    sudo -i\n    ip route delete default 2>&1 >/dev/null || true; ip route add default via #{$default_route}\n    cp /etc/resolv.conf /etc/resolv.conf.orig\n    sed 's/^nameserver.*/nameserver #{$nameserver}/' /etc/resolv.conf.orig > /etc/resolv.conf\n  SHELL\n\n  vm.hostname = name\n  vm.network 'public_network', bridge: $bridge, ip: ip\n  vm.synced_folder './shared', '/vagrant_shared'\n  vm.provider 'virtualbox' do |vb|\n    vb.memory = '4096'\n    vb.cpus = '2'\n  end\n  vm.provision 'shell', inline: script\n  vm.provision 'shell', inline: network_script, run: 'always'\nend\n\nVagrant.configure('2') do |config|\n  config.vm.box = 'generic/alpine314'\n\n  config.vm.define 'server', primary: true do |server|\n    config_vm(server_name, server_ip, server_script, server.vm)\n  end\n\n  agents.each do |agent_name, agent_ip|\n    config.vm.define agent_name do |agent|\n      config_vm(agent_name, agent_ip, agent_script, agent.vm)\n    end\n  end\nend\n```\n\nThe Kubernetes manifest to add the registry:\n\n##### registry.yaml\n```yaml\napiVersion: v1\nkind: ReplicationController\nmetadata:\n  name: kube-registry-v0\n  namespace: kube-system\n  labels:\n    k8s-app: kube-registry\n    version: v0\nspec:\n  replicas: 1\n  selector:\n    app: kube-registry\n    version: v0\n  template:\n    metadata:\n      labels:\n        app: kube-registry\n        version: v0\n    spec:\n      containers:\n      - name: registry\n        image: registry:2\n        resources:\n          limits:\n            cpu: 100m\n            memory: 200Mi\n        env:\n        - name: REGISTRY_HTTP_ADDR\n          value: :5000\n        - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY\n          value: /var/lib/registry\n        volumeMounts:\n        - name: image-store\n          mountPath: /var/lib/registry\n        ports:\n        - containerPort: 5000\n          name: registry\n          protocol: TCP\n      volumes:\n      - name: image-store\n        hostPath:\n          path: /var/lib/registry-storage\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: kube-registry\n  namespace: kube-system\n  labels:\n    app: kube-registry\n    kubernetes.io/name: \"KubeRegistry\"\nspec:\n  selector:\n    app: kube-registry\n  ports:\n  - name: registry\n    port: 5000\n    targetPort: 5000\n    protocol: TCP\n  type: LoadBalancer\n```\n"
  },
  {
    "path": "docs/howtos/docker-compose.md",
    "content": "---\ntitle: \"Using Telepresence with Docker Compose\"\nhide_table_of_contents: true\n---\n# Telepresence Docker Compose Extensions\n\nA Docker Compose file can contain extensions that Docker Compose ignores. The `telepresence compose` command functions similarly to `docker compose`, but will process any `x-tele` extensions present in the Docker Compose file or its overrides before passing the final Compose specification to Docker Compose.\n\nThe `x-tele` extensions are particularly useful when you have a set of services defined in a Docker Compose file that mirrors services running in a cluster, and you want your local services to interact with remote services or vice versa. The `x-tele` extensions enable your Compose services to either act as handlers when telepresence engages a remote service, or to temporarily act as proxies for remotely running services.\n\nThe extensions can be added directly to the `compose.yaml` file, or to a `compose.override.yaml` (merged automatically by Docker Compose).\n\n## Supported `x-tele` Extensions\n\n### Top-level Extension\nThe `x-tele` [top-level extension](../reference/compose#top-level-extension) is used to define a connection to the cluster, and to override the default mount behavior when engaging with remote services.\n\n### Service Extensions\n\nThe `x-tele` [service extensions](../reference/compose#service-extensions) are used to define the behavior of a service when engaged with a remote service.\n\nTelepresence supports the following types:\n\n| Type                                        | Local service Behavior                                          | Similar to               |\n|---------------------------------------------|-----------------------------------------------------------------|--------------------------|\n| [connect](../reference/compose#connect)     | Service has access to the cluster's resources (DNS and routing) | `telepresence connect`   |\n| [proxy](../reference/compose#proxy)         | Replaced with a proxy for a service in the cluster              | N/A                      |\n| [wiretap](../reference/compose#wiretap)     | Receives wiretapped data from a service in the cluster          | `telepresence wiretap`   |\n| [ingest](../reference/compose#ingest)       | Acts as the handler for an ingested container the cluster       | `telepresence ingest`    |\n| [intercept](../reference/compose#intercept) | Acts as the handler for an intercepted service the cluster      | `telepresence intercept` |\n| [replace](../reference/compose#replace)     | Replaces a remote container in the cluster                      | `telepresence replace`   |\n\nAll types imply a `connect`, and thus rely on the top-level `x-tele` extension that defines the connection to the cluster.\n\n## Walkthrough and Samples\nThis documentation will give some examples on how to use the `x-tele` extension using the sample Emoji application, originally developed by Buoyant.io, from the https://github.com/telepresenceio/emojivoto repository. This app is easy to deploy locally using `docker compose up` or remotely to a cluster using `kubectl apply --kustomize`.\n\n### Initial Steps\n\nThe examples assume that you have [installed](../install/manager.md) the Telepresence Traffic Manager in your cluster.\n\nWe start by ensuring that the Emojivoto application can be deployed, both locally using `docker compose up` and remotely in your cluster.\n\n#### 1. Download the Emojivoto App\n\nClone the emojivoto git repository with the following command:\n```console\n$ git clone https://github.com/telepresenceio/emojivoto.git\n```\n\n#### 2. Use the app locally\n\n```console\n$ cd emojivoto\n$ docker compose up\n```\n\nNow point your browser to http://localhost:8080/. The \"Emoji Vote\" page shows up. Try it out.\n\nTear down the local app\n```console\n$ docker compose down\n```\n\n#### 3. Use the app remotely\n\nWe Create the cluster resources by applying the `kustomize/deployment` directory using the following command:\n\n```console\n$ kubectl apply -k kustomize/deployment\nnamespace/emojivoto created\nserviceaccount/emoji created\nserviceaccount/voting created\nserviceaccount/web created\nservice/emoji created\nservice/voting created\nservice/web created\ndeployment.apps/emoji created\ndeployment.apps/vote-bot created\ndeployment.apps/voting created\ndeployment.apps/web created\n```\n\nCheck that the pods are up and running with:\n```console\n$ kubectl -n emojivoto get pod\nNAME                       READY   STATUS    RESTARTS   AGE\nemoji-7d8d6fb869-wp5kc     1/1     Running   0          23s\nvote-bot-766b9f68b-9bsqk   1/1     Running   0          23s\nvoting-7d49b58d7b-n7bc8    1/1     Running   0          23s\nweb-7cc498695b-7dtcl       1/1     Running   0          23s\n```\n\nConnect to the cluster and verify that the web service is functional. The `telepresence serve web` will start the browser with a URL that points to the \"web\" service:\n```console\n$ telepresence connect -n emojivoto --docker\n ✔ Connected to context minikube, namespace emojivoto (https://192.168.49.2:8443)           0.8s\n$ telepresence serve web\n```\n\n## Extend With \"proxy\"\n\nLet's assume that we don't want to run the \"voting\" service locally. Instead, we want to replace it with a corresponding service that runs in the cluster. In other words, we want the local service to act as a _proxy_ for the remote service.\n\nThe original `compose.yaml` file contains this:\n\n```yaml\nservices:\n  web:\n    image: ghcr.io/telepresenceio/emojivoto-web:0.3.0\n    environment:\n      - WEB_PORT=8080\n      - EMOJISVC_HOST=emoji:8080\n      - VOTINGSVC_HOST=voting:8080\n      - INDEX_BUNDLE=dist/index_bundle.js\n    ports:\n      - \"8080:8080\"\n    depends_on:\n      - voting\n      - emoji\n\n  vote-bot:\n    image: ghcr.io/telepresenceio/emojivoto-web:0.3.0\n    entrypoint: emojivoto-vote-bot\n    environment:\n      - WEB_HOST=web:8080\n    depends_on:\n      - web\n\n  emoji:\n    image: ghcr.io/telepresenceio/emojivoto-emoji:0.3.0\n    environment:\n      - GRPC_PORT=8080\n    ports:\n      - \"8081:8080\"\n\n  voting:\n    image: ghcr.io/telepresenceio/emojivoto-voting:0.3.0\n    environment:\n      - GRPC_PORT=8080\n      - POLL_FILE=/data/polls.json\n    ports:\n      - \"8082:8080\"\n    volumes:\n      - data:/data\n\nvolumes:\n  data:\n```\n\nThe proxy requires a Telepresence connection to the cluster, using the `emojivoto` namespace in this example. Connections are defined in a top-level `x-tele` extension, where each connection is identified by a name and configured with attributes that correspond to the flags used in the `telepresence connect` command. The top-level extension is structured as follows:\n\n```yaml\nx-tele:\n  connections:\n    - name: emojivoto\n      namespace: emojivoto\n```\n\nTo enable proxy functionality, an extension of type `proxy` is added to the `voting` service declaration:\n\n```yaml\nx-tele:\n  type: proxy\n  connection: emojivoto\n```\n\nThe `connection: emojivoto` field specifies that the proxy uses the `emojivoto` connection defined in the top-level extension, linking it to the `emojivoto` namespace. This field is optional when the top-level extension includes only one connection. Similarly, the `name: emojivoto` in the connection declaration is optional and only required when multiple connections are defined.\n\nThe resulting file will then look like this:\n\n```yaml\nx-tele:\n  connections:\n    - name: emojivoto\n      namespace: emojivoto\nservices:\n  web:\n    image: ghcr.io/telepresenceio/emojivoto-web:0.3.0\n    environment:\n      - WEB_PORT=8080\n      - EMOJISVC_HOST=emoji:8080\n      - VOTINGSVC_HOST=voting:8080\n      - INDEX_BUNDLE=dist/index_bundle.js\n    ports:\n      - \"8080:8080\"\n    depends_on:\n      - voting\n      - emoji\n\n  vote-bot:\n    image: ghcr.io/telepresenceio/emojivoto-web:0.3.0\n    entrypoint: emojivoto-vote-bot\n    environment:\n      - WEB_HOST=web:8080\n    depends_on:\n      - web\n\n  emoji:\n    image: ghcr.io/telepresenceio/emojivoto-emoji:0.3.0\n    environment:\n      - GRPC_PORT=8080\n    ports:\n      - \"8081:8080\"\n\n  voting:\n    x-tele:\n      type: proxy\n      connection: emojivoto\n    image: ghcr.io/telepresenceio/emojivoto-voting:0.3.0\n    environment:\n      - GRPC_PORT=8080\n      - POLL_FILE=/data/polls.json\n    ports:\n      - \"8082:8080\"\n    volumes:\n      - data:/data\n\nvolumes:\n  data:\n```\n\n> [!TIP]\n> Instead of modifying the original `compose.yaml` file, we can add a new file adjacent to it and call it `compose.override.yaml`. Docker Compose will automatically merge this override with the `compose.yaml`. So, leave original `compose.yaml` intact, and instead add a `compose.override.yaml` file with the following contents (the optional connection name and proxy connection reference are both removed):\n>\n> ```yaml\n> x-tele:\n>   connections:\n>     - namespace: emojivoto\n> services:\n>  voting:\n>    x-tele:\n>      type: proxy\n> ```\n\n### Running the Extended Sample\n\nRunning with `telepresence compose up` will discover the extension, connect to the cluster, modify an in-memory version of the Compose specification so that it no longer contains the \"voting\" service, alter the DNS so that lookups for this service instead find the one in the cluster, and configure routing so that the \"web\" service still finds the \"voting\" service. We can verify this using:\n```console\n$ telepresence compose\n ✔ Connected to context minikube, namespace emojivoto (https://192.168.49.2:8443)     2.4s \n ✔ Proxied service voting                                                             0.0s \n[+] Running 4/4\n ✔ Network emojivoto_default        Created                                           0.1s \n ✔ Container emojivoto-emoji-1  Created                                               0.0s \n ✔ Container emojivoto-web-1        Created                                           0.0s \n ✔ Container emojivoto-vote-bot-1   Created                                           0.0s \nAttaching to emoji-1, vote-bot-1, web-1\nemoji-1     | 2025/07/19 05:42:39 Starting grpc server on GRPC_PORT=[8080]\nweb-1       | 2025/07/19 05:42:39 Connecting to [voting:8080]\nweb-1       | 2025/07/19 05:42:39 Connecting to [emoji:8080]\nweb-1       | 2025/07/19 05:42:39 Starting web server on WEB_PORT=[8080] and MESSAGE_OF_THE_DAY=[]\nvote-bot-1  | ✔ Voting for :older_man:\nvote-bot-1  | ✔ Voting for :100:\nvote-bot-1  | ✔ Voting for :bulb:\n...\n```\nWe now see \"Proxied service voting\" and then, in contrast to the output from a `docker compose up`, no further output from that service. The `vote-bot-1` continues to vote though, to it's obviously still talking to a `voting`.\n\n### Takeaways\nUsing our Telepresence \"proxy\" extension, we have now successfully modified our setup so that the services in the compose.yaml file interact with a service in the cluster.\n\n### Proxy the web\n\nCan we proxy the web service and still reach it using `localhost:8080` in our browser? Let's give it a try using the following `compose.override.yaml` file:\n\n```yaml\nx-tele:\n  connections:\n    - namespace: emojivoto\nservices:\n  web:\n    x-tele:\n      type: proxy\n      ports:\n        - 8080:80\n```\n\nWorth noting here is that the original docker-compose service will expose port 8080, so that's what our proxy must expose to other containers. In the cluster, however, the web service uses port 80. This is why we need to specify the port mapping in the extension:\n```yaml\n      ports:\n        - 8080:80\n```\n\nWith this change, we can now run the sample using `telepresence compose up` and connect to the web service using `localhost:8080` in our browser.\n\n## Extend With \"replace\"\n\nOur previous example used a proxy to replace a local service with a remote service. In this sample we will do the opposite. We will make the remote services talk to services in our Docker Compose file. In essence, we will let the remote `web` and `vote-bot` service use the `emoji` and `vote` service that we run locally.\n\nOur extensions look like this:\n```yaml\nx-tele:\n  connections:\n    - namespace: emojivoto\nservices:\n  emoji:\n    x-tele:\n      type: replace\n  voting:\n    x-tele:\n      type: replace\n  vote-bot:\n    profiles:\n      - notEnabled\n```\n\n> [!NOTE]\n> The last part:\n> ```yaml\n>   vote-bot:\n>     profiles:\n>       - notEnabled\n> ```\n> effectively disables the local `vote-bot` service so that only the vote-bot running in the cluster is active. It's optional, but it makes it easier to see what happens when we run the sample:\n\n```console\n$ telepresence compose up\n ✔ Connected to context minikube, namespace emojivoto (https://192.168.49.2:8443)     2.8s \n[+] Engaging 2/2\n ✔ emoji  Replaced service emoji                                                      2.0s \n ✔ voting Replaced service voting                                                     1.6s \n[+] Running 4/4\n ✔ Network emojivoto_default    Created                                               0.0s \n ✔ Container emojivoto-voting1  Created                                               0.1s \n ✔ Container emojivoto-emoji-1  Created                                               0.1s \n ✔ Container emojivoto-web-1    Created                                               0.1s \nAttaching to emoji-1, voting-1, web-1\nemoji-1   | 2025/08/05 09:17:39 Starting prom metrics on PROM_PORT=[8801]\nemoji-1   | 2025/08/05 09:17:39 Starting grpc server on GRPC_PORT=[8080]\nvoting-1  | 2025/08/05 09:17:39 Storing votes in file /data/polls.json\nvoting-1  | 2025/08/05 09:17:39 Starting prom metrics on PROM_PORT=[8801]\nvoting-1  | 2025/08/05 09:17:39 Starting grpc server on GRPC_PORT=[8080]\nvoting-1  | 2025/08/05 09:17:39 Using failureRate [0.000000] and artificialDelayDuration [0s]\nweb-1     | 2025/08/05 09:17:39 Connecting to [voting:8080]\nweb-1     | 2025/08/05 09:17:39 Connecting to [emoji:8080]\nweb-1     | 2025/08/05 09:17:39 Starting web server on WEB_PORT=[8080] and MESSAGE_OF_THE_DAY=[]\nvoting-1  | 2025/07/20 04:39:58 Voted for [:fax:], which now has a total of [12] votes\nvoting-1  | 2025/07/20 04:39:59 Voted for [:doughnut:], which now has a total of [231] votes\nvoting-1  | 2025/07/20 04:40:00 Voted for [:flight_departure:], which now has a total of [8] votes\n```\n\nWe can observe that after an initial delay - caused by the remote vote bot reconnecting after the replacement of the vote container - the votes arrive, even though no vote-bot is running locally. Furthermore, if we start a browser on http://localhost:8080 now, we see the same leaderboard as a browser started using `telepresence serve web` which serves up the remote service.\n\nIn the cluster, the pods for the \"voting\" and \"emoji\" deployments have been replaced with traffic-agents that redirect all traffic to their corresponding \"voting\" and \"emoji\" Docker Compose service. We can easily verify this using:\n```console\n$ kubectl -n emojivoto get pod -l app=voting -o jsonpath='{.items.*.spec.containers.*.name}'\ntraffic-agent\n$ kubectl -n emojivoto get pod -l app=emoji -o jsonpath='{.items.*.spec.containers.*.name}'\ntraffic-agent\n```\n\n### Remote Mounts\n\nOne interesting observation is that the vote counts don't start from zero. Instead, they are synced with the vote counts used by the voting service in the cluster. This is because the \"replace\" extension automatically replaced the mounted \"data\" volume with a remote mount of the corresponding volume in the replaced container. This default behavior can be controlled using mount policies.\n\n#### Preventing Remote Mounts\n\nTo prevent the remote mounts from happening, and instead keep the volumes created by Docker Compose, we can add a `mounts` object to the top-level `x-tele` extension in the `compose.override.yaml` file:\n```yaml\nx-tele:\n  connections:\n    - namespace: emojivoto\n  mounts:\n    - volume: data\n      policy: local\n```\n\nThe `mounts` object is a list of objects, each with a `volume` field that corresponds to the name of the volume in the Compose file or a `volumePattern`, a regular expression that matches that name, and a `policy` field that can be set to either `local`, `ignore`, `remote` or `remoteReadOnly`. The default is to use whatever policy that the traffic-agent uses for the volume.\n\n### Takeaways\nUsing our Telepresence \"replace\" extension, we have successfully modified our setup so that multiple services in the cluster have been replaced by services that run locally as part of our Docker Compose spec.\n"
  },
  {
    "path": "docs/howtos/docker.md",
    "content": "---\ntitle: \"Using Telepresence with Docker\"\nhide_table_of_contents: true\n---\n# Telepresence with Docker\n\n## Why?\n\nIt can be tedious to adopt Telepresence across your organization, since the [package installer](../install/client.md)\nrequires organizational approval, and Telepresence needs to get along with any exotic networking setup that your\ncompany may have.\n\nIf Docker is already approved in your organization, this approach should be considered.\n\n## How?\n\nWhen using Telepresence in Docker mode, users don't need organizational approval of a package installer, can address several networking challenges, and forego the need for third-party applications to enable volume mounts.\n\nYou can simply add the docker flag to any Telepresence command, and it will start your daemon in a container,\nmaking it easier to adopt as an organization.\n\nLet's illustrate with a quick demo, assuming a default Kubernetes context named default, and a simple HTTP service:\n\n```console\n$ telepresence connect --docker\nConnected to context default, namespace default (https://kubernetes.docker.internal:6443)\n```\n\nThis method limits the scope of the potential networking issues since everything stays inside Docker. The Telepresence daemon can be found under the name `tp-<your-context>-cn` when listing your containers.\n\n```console\n$ docker ps\nCONTAINER ID   IMAGE                                        COMMAND                  CREATED          STATUS          PORTS                        NAMES\n540a3c12f45b   ghcr.io/telepresenceio/telepresence:2.22.0   \"telepresence connec…\"   18 seconds ago   Up 16 seconds   127.0.0.1:58802->58802/tcp   tp-default-cn\n```\n\nReplace a container in the cluster and start a corresponding local container:\n\n```cli\n$ telepresence replace echo-sc --docker-run -- ghcr.io/telepresenceio/echo-server:latest\nUsing Deployment echo-sc\n   Container name: echo-sc\n   State         : ACTIVE\n   Workload kind : Deployment\n   Port forwards : 127.0.0.1 -> 127.0.0.1\n       8080 -> 8080 TCP\nEcho server listening on port 8080.\n```\n\nUsing `--docker-run` starts the local container that acts as the handler, so that it uses the same network as the\ncontainer that runs the telepresence daemon. It will also receive the same incoming traffic and have the remote volumes\nmounted in the same way as the remote container that it replaces.\n\nIf you want to curl your remote service, you'll need to do that from a container that shares the daemon container's\nnetwork. Telepresence provides a `curl` command that will do just that.\n\n```console\n$ telepresence curl echo-sc\n  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n                                 Dload  Upload   Total   Spent    Left  Speed\n100   196  100   196    0     0   4232      0 --:--:-- --:--:-- --:--:--  4260\nRequest served by 540a3c12f45b\n\nIntercept id b0bd5e75-2618-4bef-ac4e-4c08c4b58ec7:echo-sc/echo-sc\nIntercepted container \"echo-sc\"\nHTTP/1.1 GET /\n\nHost: echo-sc\nUser-Agent: curl/8.11.1\nAccept: */*\n```\n\n### Starting the local container prior to the intercept\nIf you want to start your container manually using `docker run`, you must ensure that it shares the  daemon container's\nnetwork. A convenient way to do that is to use the `--docker-run` flag as explained above, but you can also start\na container separately using `telepresence docker-run`. This can be done before or after the intercept and the run will\nsurvive cycling the intercept on or off.\n\n```console\n$ telepresence docker-run ghcr.io/telepresenceio/echo-server:latest\nEcho server listening on port 8080.\n```\n\nCheck what name the started container has:\n```console\n$ docker ps --last 1 --format {{.Names}}\nfervent_goodall\n```\n\n\n\nYou can now redirect intercepted traffic to your \"echo\" container using the address flag, e.g.:\n```console\n$ telepresence intercept --port 8080:80 --address echo fervent_goodall\n```\n\n> [!TIP]\n> Name your container using the `--name` flag, e.g. `telepresence docker-run --name echo ghcr.io/telepresenceio/echo-server:latest`.\n> This will make it easier to refer to it later.\n\n> [!IMPORTANT]\n> Never name your container the same as a service in the cluster. If you do, you'll get a warning that the name overrides\n> the service IP, and the service will not be reachable.\n\n### Use named connections\nYou can use the `--name` flag to name the connection if you want to connect to several namespaces simultaneously, e.g.\n```console\n$ telepresence connect --docker --name alpha --namespace alpha\n$ telepresence connect --docker --name beta --namespace beta\n```\n\nNow, with two connections active, you must pass the flag `--use <name pattern>` to other commands, e.g.\n\n```console\n$ telepresence replace echo-easy --use alpha --docker-run -- ghcr.io/telepresenceio/echo-server:latest\n```\n\n## Key learnings\n\n* Using the Docker mode of telepresence **does not require organizational approval of a package installer**, and makes it **easier** to adopt across your organization.\n* It **limits the potential networking issues** you can encounter.\n* It **limits the potential mount issues** you can encounter.\n* It **enables simultaneous engagements in multiple namespaces**.\n"
  },
  {
    "path": "docs/howtos/engage.md",
    "content": "---\ntitle: Code and debug an application locally\ndescription: Start using Telepresence in your own environment. Follow these steps to work locally with cluster applications.\nhide_table_of_contents: true\n---\n\n# Code and debug an application locally\n\n## Local Development Methods\n\nTelepresence offers three powerful ways to develop your services locally:\n\n### Replace\n* **How it Works:**\n  - Replaces an existing container within your Kubernetes cluster with a Traffic Agent.\n  - Reroutes traffic intended for the replaced container to your local workstation.\n  - Makes the remote environment of the replaced container available to the local workstation.\n  - Provides read-write access to the volumes mounted by replaced container.\n* **Impact:**\n  - A Traffic Agent is injected into the pods of the targeted workload.\n  - The replaced container is removed from the pods of the targeted workload.\n  - The replaced container is restored when the replace operation ends.\n* **Use-cases:**\n  - You're working with message queue consumers and must stop the remote container.\n  - You're working with remote containers configured without incoming traffic.\n\n### Intercept\n* **How it Works:**\n  - Intercepts requests destined for a specific service port (or ports) and reroutes them to the local workstation.\n  - Makes the remote environment of the targeted container available to the local workstation.\n  - Provides read-write access to the volumes mounted by the targeted container.\n  - Makes it possible to filter traffic using HTTP headers and paths.\n* **Impact:**\n  - A Traffic Agent is injected into the pods of the targeted workload.\n  - Intercepted traffic is rerouted to the local workstation and will no longer reach the remote service.\n  - Only traffic that matches the intercept filters will be rerouted.\n  - All containers keep on running.\n* **Use-cases:**\n  - Your main focus is the service API rather than the cluster's pods and containers.\n  - You want your local service to only receive specific ingress traffic, while other traffic must be untouched.\n  - You want your remote container to continue processing other requests or background tasks.\n\n### Wiretap\n* **How it Works:**\n  - Adds a wiretap on a specific service port (or ports) and sends the data to the local workstation.\n  - Makes the remote environment of the targeted container available to the local workstation.\n  - Provides read-only access to the volumes mounted by the targeted container.\n  - Makes it possible to filter traffic using HTTP headers and paths.\n* **Impact:**\n  - A Traffic Agent is injected into the pods of the targeted workload.\n  - All containers keep on running.\n  - All traffic will still reach the remote service.\n  - Wiretapped traffic is rerouted to the local workstation.\n* **Use-cases:**\n  - You need a solution where several developers can engage with the same service simultaneously.\n  - Your main focus is the service API rather than the cluster's pods and containers.\n  - You want your local service to only receive specific ingress traffic.\n  - You don't care about the responses sent by your local service.\n  - You don't want breakpoints in your local service to affect the remote service.\n  - You want to keep the impact that your local development has on the cluster to a minimum.\n\n### Ingest\n* **How it Works:**\n   - Makes the remote environment of the ingested container available to the local workstation.\n   - Provides read-only access to the volumes mounted by replaced container.\n* **Impact:**\n   - A Traffic Agent is injected into the pods of the targeted workload.\n   - No traffic is rerouted and all containers keep on running.\n* **Use-cases:**\n   - You want to keep the impact that your local development has on the cluster to a minimum.\n   - You have don't need traffic being routed from the cluster, and read-only access to the container's volumes is ok.\n\n## Prerequisites\n\nBefore you begin, you need to have [Telepresence installed](../install/client.md). This document uses the Kubernetes command-line tool, [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/)\nin several examples. OpenShift users can substitute oc [commands instead](https://docs.openshift.com/container-platform/4.1/cli_reference/developer-cli-commands.html).\n\nThis guide assumes you have an application represented by a Kubernetes deployment and service accessible publicly by an ingress controller,\nand that you can run a copy of that application on your laptop.\n\n## Replace Your Container\n\nThis approach offers the benefit of direct cluster connectivity from your workstation, simplifying debugging and\nmodification of your application within its familiar environment. Note that if Telepresence was installed using a\nstandalone binary rather than a [package installer](../install/client.md), it will require root access to configure the\nnetwork interface. Remote mounts must be made relative to a specific mount point, which can add complexity.\n\n1. Connect to your cluster with `telepresence connect` and try to curl to the Kubernetes API server. A 401 or 403 response code is expected and indicates that the service could be reached:\n\n   ```console\n   $ curl -ik https://kubernetes.default\n   HTTP/1.1 401 Unauthorized\n   Cache-Control: no-cache, private\n   Content-Type: application/json\n   ...\n   ```\n\n   You now have access to your remote Kubernetes API server as if you were on the same network. You can now use any local tools to connect to any service in the cluster.\n\n2. Enter `telepresence list` and make sure the workload (deployment in this case) you want to intercept is listed. For example:\n\n   ```console\n   $ telepresence list\n   ...\n   deolpoyment example-app: ready to engage (traffic-agent not yet installed)\n   ...\n   ```\n\n3. Get the name of the container you want to replace (output truncated for brewity)\n    ```console\n    $ kubectl describe deploy example-app\n    Name:                   example-app\n    Namespace:              default\n    CreationTimestamp:      Tue, 14 Jan 2025 03:49:29 +0100\n    Labels:                 app=example-app\n    Annotations:            deployment.kubernetes.io/revision: 1\n    Selector:               app=example-app\n    Replicas:               1 desired | 1 updated | 1 total | 0 available | 1 unavailable\n    StrategyType:           RollingUpdate\n    MinReadySeconds:        0\n    RollingUpdateStrategy:  25% max unavailable, 25% max surge\n    Pod Template:\n      Labels:  app=example-app\n      Containers:\n       echo-server:\n        Image:      ghcr.io/telepresencio/echo-server\n        Port:       8080/TCP\n    ```\n\n4. Replace the container. Please note that the `--container echo-server` flag here is optional. It's only needed when the workload has more than one container:\n   ```console\n   $ telepresence replace example-app --container echo-server --env-file /tmp/example-app.env --mount /tmp/example-app-mounts\n   Using Deployment example-app\n   Container name    : echo-server\n   State             : ACTIVE\n   Workload kind     : Deployment\n   Port forwards     : 10.1.4.106 -> 127.0.0.1\n       8080 -> 8080 TCP\n   Volume Mount Point: /tmp/example-app-mounts\n   ```\n   Your workstation is now ready. You can run the application using the environment in the `/tmp/example-app.env` file and the\n   mounts under `/tmp/example-app-mounts`. The application can listen to `localhost:8080` to receive traffic intended for the\n   replaced container. On the cluster side of things, a Traffic Agent container has replaced the `echo-server`.\n\n   Telepresence assumes that you want all declared container ports to be mapped to their corresponding port on `localhost`. You\n   can change this with the `--port` flag. For example, `--port 1080:8080` will map the replaced containers port number `8080`\n   to `localhost:1080`. The `--port` can also be used when the container is known to listen to ports that are not declared in\n   the manifest.\n\n5. Query the cluster in which you replaced your application and verify your local instance being invoked. All the traffic previously routed to your Kubernetes Service is now routed to your local environment\n\nYou can now:\n- Make changes on the fly and see them reflected when interacting with your Kubernetes environment.\n- Query services only exposed in your cluster's network.\n- Set breakpoints in your IDE to investigate bugs.\n\n6. You end the replace operation with the command `telepresence leave example-app --container echo-server`\n\n## Ingest Your Container\n\nIn some situations, you want to work and debug the code locally, and you want it to be able to access other services in the cluster,\nbut you don't wish to interfere with the targeted workload. This is where the `telepresence ingest` command comes into play. Just\nlike `replace` command, it will make the environment and mounted containers of the targeted container available locally, but it will\nnot replace the container nor will it intercept any of its traffic.\n\nThis example assumes that you have the `example-app` deployment.\n\n1. Connect and run and start an ingest from `example-app`:\n   ```console\n   $ telepresence connect\n   Launching Telepresence User Daemon\n   Launching Telepresence Root Daemon\n   Connected to context xxx, namespace default (https://<some url>)\n   $ telepresence ingest example-app --container echo-server --env-file /tmp/example-app.env --mount /tmp/example-app-mounts\n   Using Deployment example-app\n      Container name    : echo-server\n      Workload kind     : Deployment\n      Volume Mount Point: /tmp/example-app-mounts\n   ```\n\n2. Start your local application using the environment variables retrieved and the volumes that were mounted in the previous step.\n\nYou can now:\n- Code and debug your local app while it interacts with other services in your cluster.\n- Query services only exposed in your cluster's network.\n- Set breakpoints in your IDE to investigate bugs.\n\n## Intercept Your Application\n\nThe `telepresence intercept` command allows you to redirect traffic for a specific service to your local workstation.\nCompared to the replace command, intercept is less invasive because it: a) enables precise filtering of intercepted\ntraffic using HTTP headers or paths, and b) allows the original service to continue running, handling all other traffic\nand tasks not directly related to the intercepted traffic.\n\n1. Connect to your cluster with `telepresence connect`.\n\n2. Intercept all traffic going to the application's http port in your cluster and redirect to port 8080 on your workstation.\n    ```console\n    $ telepresence intercept example-app --http-header 'x-user=margret' --http-path-prefix '/api' --port 8080:http --env-file ~/example-app-intercept.env --mount /tmp/example-app-mounts\n    Using Deployment example-app\n    intercepted\n      Intercept name: example-app\n      State         : ACTIVE\n      Workload kind : Deployment\n      Destination   : 127.0.0.1:8080\n      Intercepting  : HTTP requests with path-prefix /api and header 'X-User: margret'\n    ```\n\n   * For `--http-header`: specify the HTTP header you want to filter on. You can specify multiple headers by repeating the flag. Header-based intercepts take priority over path-only intercepts, so that when multiple intercepts are active on the same workload, requests are evaluated against header-based filters first, then path-only filters. This allows different developers to use header-based personal intercepts (e.g., `x-user=alice`) while others use path-based intercepts (e.g., `--http-path-prefix /admin/`) without conflicts.\n\n   * For '--http-path-prefix': specify the path prefix you want to filter on. You can specify multiple path prefixes by repeating the flag. Path-based intercepts have lower priority than header-based intercepts.\n\n   * For `--port`: specify the port the local instance of your application is running on, and optionally the remote port that you want to intercept. Telepresence will select the remote port automatically when there's only one service port available to access the workload. You must specify the port to intercept when the workload exposes multiple ports. You can do this by specifying the port you want to intercept after a colon in the `--port` argument (like in the example), and/or by specifying the service you want to intercept using the `--service` flag.\n\n   * For `--env-file`: specify a file path for Telepresence to write the environment variables that are set for the targeted container.\n\n3. Start your local application using the environment variables retrieved and the volumes that were mounted in the previous step.\n\nYou can now:\n- Make changes on the fly and see them reflected when interacting with your Kubernetes environment without affecting other users of the same service.\n- Query services that are only exposed in your cluster's network.\n- Set breakpoints in your IDE to investigate bugs.\n\n## Wiretap Your Application\n\nYou can use the `telepresence wiretap` command when you want to wiretap the traffic for a specific service and send a\ncopy of it to your workstation. The `wiretap` is less intrusive than the `intercept`, because it does not interfere\nwith the traffic at all.\n\n1. Connect to your cluster with `telepresence connect`.\n\n2. Put a wiretap on all traffic going to the application's http port in your cluster and send it to port 8080 on your workstation.\n    ```console\n    $ telepresence wiretap example-app --port 8080:http --env-file ~/example-app-intercept.env --mount /tmp/example-app-mounts\n    Using Deployment example-app\n    wiretapped\n      Wiretap name  : example-app\n      State         : ACTIVE\n      Workload kind : Deployment\n      Destination   : 127.0.0.1:8080\n      Intercepting  : all TCP connections\n    ```\n\n    * For `--port`: specify the port the local instance of your application is running on, and optionally the remote port\n      that you want to wiretap. Telepresence will select the remote port automatically when there's only one service\n      port available to access the workload. You must specify the port to wiretap when the workload exposes multiple\n      ports. You can do this by specifying the port you want to wiretap after a colon in the `--port` argument (like in\n      the example), and/or by specifying the service you want to wiretap using the `--service` flag.\n\n    * For `--env-file`: specify a file path for Telepresence to write the environment variables that are set for the targeted\n      container.\n\n3. Start your local application using the environment variables retrieved and the volumes that were mounted in the previous step.\n\nYou can now:\n- Query services only exposed in your cluster's network.\n- Set breakpoints in your IDE to investigate bugs.\n\n### Running Everything Using Docker\n\nThis approach confines the Telepresence network interface and remote mounts to a container, and like the\n[package installer](../install/client.md) approach, eliminates the need for root access.  Additionally, it allows for precise replication of the target container's volume mounts, using identical \nmount points. However, this method will require docker to get cluster connectivity, and the containerized environment can\npresent challenges in terms of toolchain integration, debugging, and the overall development workflow.\n\n1. Connect to your cluster with `telepresence connect --docker`. This starts the Telepresence daemon in a docker\n   container and ensures that this container has access to the cluster network.\n2. Use `telepresence curl` to access the Kubernetes API server from a container.\n   A 401 or 403 response code is expected and indicates that the service could be reached. The `telepresence curl` command \n   used will execute a standard `curl` command from a container that shares the network created by the `connect` call:\n\n   ```console\n   $ telepresence curl -ik https://kubernetes.default\n   HTTP/1.1 401 Unauthorized\n   Cache-Control: no-cache, private\n   Content-Type: application/json\n   ...\n   ```\n\n   You now have access to your remote Kubernetes API server as if you were on the same network.\n\n3. Enter `telepresence list` and make sure the workload you want to engage is listed. For example:\n\n   ```console\n   $ telepresence list\n   ...\n   deployment example-app: ready to engage (traffic-agent not yet installed)\n   ...\n   ```\n\n4. Use `replace`, `inject`, or `intercept` to engage the container in combination with the `--docker-run` flag.\n   Example using `telepresence replace`\n\n    ```console\n    $ telepresence replace example-app --container echo-server --docker-run -- <your local container>\n    Using Deployment example-app\n    intercepted\n      Intercept name: example-app\n      State         : ACTIVE\n      Workload kind : Deployment\n      Destination   : 127.0.0.1:8080\n      Intercepting  : all TCP connections\n    <output from your local container>\n    ```\n\nYou can now:\n- Make changes on the fly and see them reflected when interacting with your Kubernetes environment; although\n  depending on how your local container is configured, this might require that it is rebuilt.\n- Query services only exposed in your cluster's network using `telepresence curl`.\n- Set breakpoints in a _Remote Debug_ configuration in your IDE to investigate bugs.\n"
  },
  {
    "path": "docs/howtos/large-clusters.md",
    "content": "---\ntitle: Work with large clusters\ndescription: Use Telepresence to intercept services in clusters with a large number of namespaces and workloads.\nhide_table_of_contents: true\n---\n# Working with large clusters\n\n## Large number of namespaces\n\n### The problem\nWhen telepresence connects to a cluster, it will configure the local DNS server so that each namespace in the cluster can be used as a top-level domain (TLD). E.g. if the cluster contains the namespace \"example\", then a curl for the name \"my_service.example\" will be directed to Telepresence DNS server, because it has announced that it wants to resolve the \"example\" domain.\n\nTelepresence tries to be conservative about what namespaces that it will create TLDs for, and first check if the namespace is accessible by the user. This check can be time-consuming in a cluster with a large number of namespaces, because each check will typically take up to a second to complete, which means that for a cluster with 120 namespaces, this check can take two minutes. That's a long time to wait when doing `telepresence connect`.\n\n### How to solve it\n\n#### Limiting at connect\n\nThe `telepresence connect` command will accept the flag `--mapped-namespaces <comma separated names>`, which will limit the names that Telepresence create TLDs for in the DNS resolver. This may drastically decrease the time it takes to connect, and also improve the DNS resolver's performance.\n\n#### Limiting the traffic-manager\n\nIt is possible to limit the namespaces that the traffic-manager will care about when it is installed or upgraded by passing the Helm chart value `namespaces` or `namespaceSelector`. This will tell the manager to only manage those namespaces with respect to connects and engagements. A namespace-limited manager creates an implicit `mapped-namespaces` set for all clients that connect to it.\n\n## Large number of pods\n\n### The problem\n\nA cluster with a large number of pods can be problematic in situations where the traffic-manager is unable to use its default behavior of retrieving the pod-subnets from the cluster nodes. The manager will then use a fallback method, which is to retrieve the IP of all pods and then use those IPs to calculate the pod-subnets. This in turn, might cause a very large number of requests to the Kubernetes API server.\n\n### The solution\n\nIf it is RBAC permission limitations that prevent the traffic-manager from reading the `podCIDR` from the nodes, then adding the necessary permissions might help. But in many cases, the nodes will not have a `podCIDR` defined. The fallback for such cases is to specify the `podCIDRs` manually (and thus prevent the scan + calculation) using the Helm chart values:\n\n```yaml\npodCIDRStrategy: environment\npodCIDRs:\n  - <known podCIDR>\n...\n```\n\n## Traffic Manager Namespaces\n\nDepending on use-case, it's sometimes beneficial to have several Traffic Managers installed, each being responsible from\na limited number of namespaces and prohibited from accessing other namespaces. A cluster can have any number of Traffic\nManagers, as long as each one manages its own unique set of namespaces.\n\nA client that connects to a Traffic Manager will automatically be limited to its managed namespaces.\n\nSee [Installing a namespaced-scoped traffic-manager](../install/manager.md#limiting-the-namespace-scope) for details.\n"
  },
  {
    "path": "docs/howtos/mtls.md",
    "content": "---\ntitle: Intercepting Applications Using TLS/mTLS\ndescription: How to perform HTTP-filtered intercepts with encrypted data\nhide_table_of_contents: true\n---\n\n# Intercepting TLS/mTLS Applications \n\n## Overview\n\nTelepresence requires access to HTTP headers and paths to perform HTTP-filtered intercepts. When traffic is encrypted with TLS/mTLS, Telepresence must decrypt the data to inspect these headers. This document explains how to configure Telepresence to handle encrypted traffic.\n\n## Decrypting Traffic\n\nTo decrypt TLS/mTLS traffic, Telepresence needs access to the TLS certificates used by your application. You can provide this access in two ways:\n\n1. **Mount existing volumes**: Use certificates already mounted in a volume by your application.\n2. **Reference a secret**: Mount a Kubernetes secret containing the certificate directly.\n\n### Option 1: Using a Mounted Certificate\n\nIf your application mounts a volume containing TLS certificates, the Telepresence traffic-agent automatically mounts the same volume. You only need to specify the certificate's path using an annotation.\n\n#### Example\nSuppose your application defines a `tls` volume for the secret `tel-cert`, which contains `tls.crt` and `tls.key`:\n\n```yaml\nvolumes:\n  - name: tls\n    secret:\n      secretName: tel-cert\n```\n\nThe volume is mounted at `/etc/certs`:\n\n```yaml\n        volumeMounts:\n          - name: tls\n            mountPath: /etc/certs\n            readOnly: true\n```\n\nAdd the following annotation to your workload to enable Telepresence to use this certificate:\n\n```yaml\n  template:\n    metadata:\n      annotations:\n        telepresence.io/downstream-tls-path.8443: /etc/certs\n```\n\nThis annotation directs Telepresence to use the certificate at `/etc/certs` for decrypting traffic on port 8443.\n\n**Notes:**\n- For multiple ports, repeat the annotation with different port suffixes.\n- Ensure the certificate is mounted by all containers whose ports are specified in the annotation.\n\n### Using a Secret\n\nIf your application containers do not mount the TLS certificate, the traffic-agent can independently mount a Kubernetes secret. The secret must reside in the same namespace as the workload.\n\nAdd the following annotation to your workload:\n```yaml\n  template:\n    metadata:\n      annotations:\n        telepresence.io/downstream-tls-secret.8443: secret-name\n```\n\nThis annotation prompts the traffic-agent injector to:\n1. Add a volume for the specified secret to the pod\n2. Mount that volume where the traffic-agent can access the certificate\n\n## Encrypting Upstream Traffic\n\nAfter decrypting traffic and inspecting HTTP filters, Telepresence must re-encrypt the traffic before forwarding it to the application. For applications requiring mutual TLS (mTLS), Telepresence must use the client-side TLS certificate for the upstream connection. Use annotations similar to those for decrypting traffic, but with the prefix `telepresence.io/upstream-tls-` instead of `telepresence.io/downstream-tls-`.\n\n### Self-Signed Certificates\nSelf-signed certificates are common in development environments, and services that use them can be accessed using `curl --insecure` or `curl -k`. Telepresence cannot detect whether this option was used. If downstream traffic is decrypted for HTTP filtering and the application uses a self-signed certificate, Telepresence will fail to establish a secure upstream connection unless verification is skipped.\n\nTo bypass verification for self-signed certificates, add the following annotation to the workload:\n```yaml\ntelepresence.io/upstream-insecure-skip-verify.<port>: enabled\n```\n\n## Using the --plaintext option\n\nThe `--plaintext` option for intercepts or wiretaps disables encryption of traffic sent to the client during an intercept or wiretap.\n\n## Protocol Selection\n\nTelepresence supports HTTP-filtered intercepts on both clear-text and TLS encrypted ports that use either HTTP/1.x and HTTP/2. Telepresence will automatically detect the protocol used by the application and use the appropriate protocol for the intercept. This is done by inspecting the [appProtocol](https://kubernetes.io/docs/concepts/services-networking/service/#application-protocol) of the Kubernetes service for the workload, or if that is not set, by looking at the port name and number or by probing the application. See below for more details.\n\nTelepresence will never use TLS or HTTP/2 for services that have a `appProtocol` that is `tcp` or `udp`. Those values imply a \"no app-layer sniffing\" mode, so when set, they effectively rule out all uses of HTTP-filtered intercepts on encrypted traffic.\n\n### TLS Detection\nTelepresence will always use TLS for service ports that have:\n\n- A TLS certificate configured using the `telepresence.io/downstream-tls-path.<port>` or `telepresence.io/downstream-tls-secret.<port>` annotation.`\n- An `appProtocol` that is `kubernetes.io/wss`, `wss`, `http2`, `https`, or `grpc`.\n- The name `https`.\n- The number 443.\n\nTelepresence will never use TLS for service ports that have a `appProtocol` that is `kubernetes.io/ws`, `kubernetes.io/h2c`, or `h2c`.\n\nIf the selection cannot be made from the above criteria, Telepresence will probe the application to determine if TLS is used.\n\n> [!NOTE]\n> Telepresence will never care about encryption on UDP ports because HTTP filters are only supported on TCP connections. Encrypted UDP traffic can still be intercepted, but the intercept must be global as it happens in the Transport Layer.\n\n### HTTP/2 Detection\n\nTelepresence will always use HTTP/2 for services that have `appProtocol` that is \"kubernetes.io/h2c\", \"h2c\", \"http2\", or \"grpc\".\n\nIf the selection cannot be made from the above criteria, Telepresence will probe the application to determine if HTTP/2 is used.\n\n### Probing\n\nProbing is a fallback mechanism for determining the protocol. It is recommended that it is avoided by setting the appropriate [appProtocol](https://kubernetes.io/docs/concepts/services-networking/service/#application-protocol). The probing takes place when the first connection is made to an intercepted service.\n\nIf the probe times out because the application is slow to start, the default timeout of 2 seconds can be overridden with the annotation `telepresence.io/upstream-probe-timeout.<port>`. The value must be a number followed by a duration unit, such as `10s` or `500ms`.\n"
  },
  {
    "path": "docs/install/client.md",
    "content": "---\ntitle: Install client\nhide_table_of_contents: true\n---\n\n\nimport Platform from '@site/src/components/Platform';\n\n# Client Installation\n\nInstall the Telepresence client on your workstation by running the commands below for your OS.\n\n<Platform.Provider>\n<Platform.TabGroup>\n<Platform.MacOSTab>\n\n## Install with the package installer (Recommended)\n\nThe package installer sets up the root daemon as a system service via launchd, eliminating the need for elevated\nprivileges when using Telepresence.\n\nDownload the appropriate installer for your architecture:\n- [telepresence-darwin-amd64.pkg](https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-darwin-amd64.pkg) (Intel Macs)\n- [telepresence-darwin-arm64.pkg](https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-darwin-arm64.pkg) (Apple Silicon Macs)\n\nDouble-click the downloaded `.pkg` file and follow the installation prompts. You can optionally deselect the root\ndaemon component during installation if you prefer to run it manually.\n\n### Allowing the system extension\n\nAfter installation, macOS will block the root daemon from running until you explicitly allow it. You need to:\n\n1. Open **System Settings**\n2. Go to **General > Login Items & Extensions**\n3. Find **Tada AB** in the list and enable it\n\nThe macOS installer is signed and distributed by Tada AB, a Swedish company founded by the lead maintainer of the\nTelepresence open source project. This approval is required because the root daemon manages virtual network\ninterfaces and DNS on your workstation, which macOS treats as a privileged operation.\n\n> [!NOTE]\n> If you skip this step, the root daemon service will not start and Telepresence will fall back to requesting\n> elevated privileges each time you run `telepresence connect`.\n\n## OR install with Homebrew\n\n```shell\nbrew install telepresenceio/telepresence/telepresence-oss\n```\n\n> [!NOTE]\n> Homebrew installs the standalone binary only. The root daemon is not installed as a system service, so\n> Telepresence will request elevated privileges when connecting.\n\n## OR download the binary manually\n\n### Intel Macs\n\n```shell\n# 1. Download the binary.\nsudo curl -fL https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-darwin-amd64 -o /usr/local/bin/telepresence\n\n# 2. Make the binary executable:\nsudo chmod a+x /usr/local/bin/telepresence\n```\n\n### Apple Silicon Macs\n\n```shell\n# 1. Ensure that no old binary exists. This is very important because Apple Silicon macs track the executable's\n# signature and just updating it in place will not work.\nsudo rm -f /usr/local/bin/telepresence\n\n# 2. Download the binary.\nsudo curl -fL https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-darwin-arm64 -o /usr/local/bin/telepresence\n\n# 3. Make the binary executable:\nsudo chmod a+x /usr/local/bin/telepresence\n```\n\n</Platform.MacOSTab>\n<Platform.GNULinuxTab>\n\n## Install using a package manager (Recommended)\n\nThe package installers set up the root daemon as a systemd service, eliminating the need for elevated privileges\nwhen using Telepresence.\n\n### Debian/Ubuntu (.deb)\n\n```shell\n# Download the latest .deb package\n# AMD64\ncurl -fLO https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-linux-amd64.deb\n# ARM64\ncurl -fLO https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-linux-arm64.deb\n\n# Install the package\nsudo apt install ./telepresence-linux-*.deb\n```\n\n### Fedora/RHEL (.rpm)\n\n```shell\n# Download the latest .rpm package\n# AMD64\ncurl -fLO https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-linux-amd64.rpm\n# ARM64\ncurl -fLO https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-linux-arm64.rpm\n\n# Install the package\nsudo dnf install ./telepresence-linux-*.rpm\n```\n\n### Viewing service logs\n\nThe root daemon service logs to the systemd journal:\n\n```shell\njournalctl -u telepresence-rootd\n```\n\n> [!NOTE]\n> If you get a permission error, your user needs to be in a group that can read the journal.\n> On Fedora/RHEL, add yourself to the `wheel` group. On Debian/Ubuntu, use `systemd-journal` or `adm`:\n> ```shell\n> sudo usermod -aG systemd-journal $USER\n> ```\n> Log out and back in for the change to take effect.\n\n## OR download the binary manually\n\n```shell\n# 1. Download the latest binary (~95 MB):\n# AMD64\nsudo curl -fL https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-linux-amd64 -o /usr/local/bin/telepresence\n\n# ARM64\nsudo curl -fL https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-linux-arm64 -o /usr/local/bin/telepresence\n\n# 2. Make the binary executable:\nsudo chmod a+x /usr/local/bin/telepresence\n```\n\n> [!NOTE]\n> Installing the standalone binary does not set up the root daemon as a system service. Telepresence will\n> request elevated privileges when connecting.\n\n</Platform.GNULinuxTab>\n<Platform.WindowsTab>\n\n## Install using the setup installer (Recommended)\n\nThe setup installer sets up the root daemon as a Windows service, eliminating the need for elevated privileges\nwhen using Telepresence. It also bundles WinFSP and SSHFS-Win for volume mount support.\n\nDownload and run the installer:\n- [telepresence-windows-amd64-setup.exe](https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-windows-amd64-setup.exe)\n\nDuring installation, you can optionally configure the daemon port and log level. The defaults work for most users.\nYou can also deselect the \"Telepresence Network Service\" feature if you prefer to run the daemon manually.\n\n> [!NOTE]\n> The Windows installer is currently only available for AMD64. For ARM64, use the manual installation method below.\n\n## OR install manually using PowerShell\n\n### Windows AMD64\n\n```powershell\n# To install Telepresence, run the following commands\n# from PowerShell as Administrator.\n\n# 1. Download the latest windows zip containing telepresence.exe and its dependencies (~60 MB):\n$ProgressPreference = 'SilentlyContinue'\nInvoke-WebRequest https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-windows-amd64.zip -OutFile telepresence.zip\n\n# 2. Unzip the telepresence.zip file to the desired directory, then remove the zip file:\nExpand-Archive -Path telepresence.zip -DestinationPath telepresenceInstaller/telepresence\nRemove-Item 'telepresence.zip'\ncd telepresenceInstaller/telepresence\n\n# 3. Run the install-telepresence.ps1 to install telepresence's dependencies. It will install telepresence to\n# C:\\telepresence by default, but you can specify a custom path by passing in -Path C:\\my\\custom\\path\npowershell.exe -ExecutionPolicy bypass -c \" . '.\\install-telepresence.ps1';\"\n\n# 4. Remove the unzipped directory:\ncd ../..\nRemove-Item telepresenceInstaller -Recurse -Confirm:$false -Force\n\n# 5. Telepresence is now installed and you can use telepresence commands in PowerShell.\n```\n\n### Windows ARM64\n\n```powershell\n# To install Telepresence, run the following commands\n# from PowerShell as Administrator.\n\n# 1. Download the latest windows zip containing telepresence.exe and its dependencies (~60 MB):\n$ProgressPreference = 'SilentlyContinue'\nInvoke-WebRequest https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-windows-arm64.zip -OutFile telepresence.zip\n\n# 2. Unzip the telepresence.zip file to the desired directory, then remove the zip file:\nExpand-Archive -Path telepresence.zip -DestinationPath telepresenceInstaller/telepresence\nRemove-Item 'telepresence.zip'\ncd telepresenceInstaller/telepresence\n\n# 3. Run the install-telepresence.ps1 to install telepresence's dependencies. It will install telepresence to\n# C:\\telepresence by default, but you can specify a custom path by passing in -Path C:\\my\\custom\\path\npowershell.exe -ExecutionPolicy bypass -c \" . '.\\install-telepresence.ps1';\"\n\n# 4. Remove the unzipped directory:\ncd ../..\nRemove-Item telepresenceInstaller -Recurse -Confirm:$false -Force\n\n# 5. Telepresence is now installed and you can use telepresence commands in PowerShell.\n```\n\n> [!NOTE]\n> Manual installation does not set up the root daemon as a Windows service or install WinFSP/SSHFS-Win.\n> Telepresence will request elevated privileges when connecting, and volume mounts will not work without\n> WinFSP and SSHFS-Win installed separately.\n\n</Platform.WindowsTab>\n</Platform.TabGroup>\n\n> [!TIP]\n> What's Next?\n> Follow one of our [quick start guides](../quick-start.md) to start using Telepresence, either with our sample app or in your own environment.\n\n## Uninstalling\n\n<Platform.TabGroup>\n<Platform.MacOSTab>\n\nIf you installed using the package installer, run the uninstall script:\n\n```shell\nsudo telepresence-uninstall\n```\n\nThis removes the Telepresence binaries and the root daemon launchd service.\n\nIf you installed with Homebrew:\n\n```shell\nbrew uninstall telepresenceio/telepresence/telepresence-oss\n```\n\n</Platform.MacOSTab>\n<Platform.GNULinuxTab>\n\nUse your package manager to remove Telepresence:\n\n```shell\n# Debian/Ubuntu\nsudo apt remove telepresence\n\n# Fedora/RHEL\nsudo dnf remove telepresence\n```\n\nThis stops and removes the root daemon systemd service and the Telepresence binaries.\n\n</Platform.GNULinuxTab>\n<Platform.WindowsTab>\n\nOpen **Settings > Apps > Installed apps**, find Telepresence, and select **Uninstall**.\n\nThis removes the Telepresence binaries, the root daemon Windows service, and the bundled WinFSP and SSHFS-Win\ncomponents.\n\n</Platform.WindowsTab>\n</Platform.TabGroup>\n\n## Installing older versions of Telepresence\n\nUse these URLs to download an older version for your OS (including older nightly builds), replacing `X.Y.Z` with the version you want.\n\n<Platform.TabGroup>\n<Platform.MacOSTab>\n\n```shell\n# Intel Macs\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-darwin-amd64\n\n# Apple Silicon Macs\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-darwin-arm64\n\n# Package installers (available from v2.27.0)\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-darwin-amd64.pkg\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-darwin-arm64.pkg\n```\n\n</Platform.MacOSTab>\n<Platform.GNULinuxTab>\n\n```\n# AMD64\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-linux-amd64\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-linux-amd64.deb\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-linux-amd64.rpm\n\n# ARM64\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-linux-arm64\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-linux-arm64.deb\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-linux-arm64.rpm\n```\n\n</Platform.GNULinuxTab>\n<Platform.WindowsTab>\n\n```\n# Windows AMD64\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-windows-amd64-setup.exe\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-windows-amd64.zip\n\n# Windows ARM64\nhttps://github.com/telepresenceio/telepresence/releases/download/vX.Y.Z/telepresence-windows-arm64.zip\n```\n\n</Platform.WindowsTab>\n</Platform.TabGroup>\n</Platform.Provider>"
  },
  {
    "path": "docs/install/cloud.md",
    "content": "---\ntitle: Cloud Provider Prerequisites\nhide_table_of_contents: true\n---\n\n# Provider Prerequisites for Traffic Manager\n\n## GKE\n\n### Firewall Rules for private clusters\n\nA GKE cluster with private networking will come preconfigured with firewall rules that prevent the Traffic Manager's\nwebhook injector from being invoked by the Kubernetes API server.\nFor Telepresence to work in such a cluster, you'll need to [add a firewall rule](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules) allowing the Kubernetes masters to access TCP port `8443` in your pods.\nFor example, for a cluster named `tele-webhook-gke` in region `us-central1-c1`:\n\n```bash\n$ gcloud container clusters describe tele-webhook-gke --region us-central1-c | grep masterIpv4CidrBlock\n  masterIpv4CidrBlock: 172.16.0.0/28 # Take note of the IP range, 172.16.0.0/28\n\n$ gcloud compute firewall-rules list \\\n    --filter 'name~^gke-tele-webhook-gke' \\\n    --format 'table(\n        name,\n        network,\n        direction,\n        sourceRanges.list():label=SRC_RANGES,\n        allowed[].map().firewall_rule().list():label=ALLOW,\n        targetTags.list():label=TARGET_TAGS\n    )'\n\nNAME                                  NETWORK           DIRECTION  SRC_RANGES     ALLOW                         TARGET_TAGS\ngke-tele-webhook-gke-33fa1791-all     tele-webhook-net  INGRESS    10.40.0.0/14   esp,ah,sctp,tcp,udp,icmp      gke-tele-webhook-gke-33fa1791-node\ngke-tele-webhook-gke-33fa1791-master  tele-webhook-net  INGRESS    172.16.0.0/28  tcp:10250,tcp:8443            gke-tele-webhook-gke-33fa1791-node\ngke-tele-webhook-gke-33fa1791-vms     tele-webhook-net  INGRESS    10.128.0.0/9   icmp,tcp:1-65535,udp:1-65535  gke-tele-webhook-gke-33fa1791-node\n# Take note fo the TARGET_TAGS value, gke-tele-webhook-gke-33fa1791-node\n\n$ gcloud compute firewall-rules create gke-tele-webhook-gke-webhook \\\n    --action ALLOW \\\n    --direction INGRESS \\\n    --source-ranges 172.16.0.0/28 \\\n    --rules tcp:8443 \\\n    --target-tags gke-tele-webhook-gke-33fa1791-node --network tele-webhook-net\nCreating firewall...⠹Created [https://www.googleapis.com/compute/v1/projects/datawire-dev/global/firewalls/gke-tele-webhook-gke-webhook].\nCreating firewall...done.\nNAME                          NETWORK           DIRECTION  PRIORITY  ALLOW     DENY  DISABLED\ngke-tele-webhook-gke-webhook  tele-webhook-net  INGRESS    1000      tcp:8443        False\n```\n\n### GKE Authentication Plugin\n\nStarting with Kubernetes version 1.26 GKE will require the use of the [gke-gcloud-auth-plugin](https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke).\nYou will need to install this plugin to use Telepresence with Docker while using GKE. \n\n## EKS\n\n### EKS Authentication Plugin\n\nIf you are using AWS CLI version earlier than `1.16.156` you will need to install [aws-iam-authenticator](https://docs.aws.amazon.com/eks/latest/userguide/install-aws-iam-authenticator.html).\nYou will need to install this plugin to use Telepresence with Docker while using EKS."
  },
  {
    "path": "docs/install/manager.md",
    "content": "---\ntitle: Install Traffic Manager\nhide_table_of_contents: true\n---\n\n# Install/Uninstall the Traffic Manager\n\nTelepresence uses a traffic manager to send/receive cloud traffic to the user. Telepresence uses [Helm](https://helm.sh) under the\nhood to install the traffic manager in your cluster. The `telepresence` binary embeds both `helm` and a helm-chart for a\ntraffic-manager that is of the same version as the binary.\n\nThe Telepresence Helm chart documentation is published at [ArtifactHUB](https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss).\n\nYou can also use `helm` command directly, see [Install With Helm](#install-with-helm) for more details.\n\n## Prerequisites\n\nBefore you begin, you need to have [Telepresence installed](../install/client.md).\n\nIf you are not the administrator of your cluster, you will need [administrative RBAC permissions](../reference/rbac.md#administrating-telepresence) to install and use Telepresence in your cluster.\n\nIn addition, you may need certain prerequisites depending on your cloud provider and platform.\nSee the [cloud provider installation notes](../install/cloud.md) for more.\n\n## Install the Traffic Manager\n\nThe telepresence cli can install the traffic manager for you. The basic install will install the same version as the client used.\n\n1. Install the Telepresence Traffic Manager with the following command:\n\n   ```shell\n   telepresence helm install\n   ```\n\n### Customizing the Traffic Manager.\n\nFor details on what the Helm chart installs and what can be configured, see the Helm chart [configuration on artifacthub](https://artifacthub.io/packages/helm/datawire/telepresence).\n\n1. Create a values.yaml file with your config values.\n\n2. Run the `install` command with the `--values` flag set to the path to your values file:\n\n   ```shell\n   telepresence helm install --values values.yaml\n   ```\n   alternatively, provide values using the `--set` flag:\n   ```shell\n   telepresence helm install --set logLevel=debug\n   ```\n\n### Install into custom namespace\n\nThe Helm chart supports being installed into any namespace, not necessarily `ambassador`. Simply pass a different `namespace` argument to\n`telepresence helm install`.  For example, if you wanted to deploy the traffic manager to the `staging` namespace:\n\n```shell\ntelepresence helm install traffic-manager --namespace staging datawire/telepresence\n```\n\n> [!NOTE]\n> If you have several traffic-managers installed, or if users don't have permissions to list\n> namespaces, they will need to either use a `--manager-namespace <namespace>` flag when connecting or\n> configure their config.yml or kubeconfig to find the desired installation of the Traffic Manager\n\nAs kubeconfig extension:\n```yaml\napiVersion: v1\nclusters:\n- cluster:\n    server: https://127.0.0.1\n    extensions:\n    - name: telepresence.io\n      extension:\n        cluster:\n          defaultManagerNamespace: staging\n  name: example-cluster\n```\n\nor in the `config.yml`:\n\n```yaml\ncluster:\n defaultManagerNamespace: staging\n```\n\nSee [the kubeconfig documentation](../reference/config.md#manager) for more information.\n\n## Upgrading/Downgrading the Traffic Manager.\n\n1. Download the cli of the version of Telepresence you wish to use.\n\n2. Run the `upgrade` command. Optionally with `--values` and/or `--set` flags \n\n   ```shell\n   telepresence helm upgrade\n   ```\n   You can also use the `--reuse-values` or `--reset-values` to specify if previously installed values should be reused or reset.\n\n\n## Uninstall\n\nThe telepresence cli can uninstall the traffic manager for you using the `telepresence helm uninstall`.\n\n1. Uninstall the Telepresence Traffic Manager and all the agents installed by it using the following command:\n\n   ```shell\n   telepresence helm uninstall\n   ```\n## Limiting the Namespace Scope\n\nYou might not want the Traffic Manager to have permissions across the entire kubernetes cluster, or you might want to be able to install multiple traffic managers per cluster (for example, to separate them by environment).\nIn these cases, the traffic manager supports being installed with a namespace scope, allowing cluster administrators to limit the reach of a traffic manager's permissions.\n\nFor example, suppose you want a Traffic Manager that only works on namespaces `dev` and `staging`.\nTo do this, create a `values.yaml` like the following:\n\n```yaml\nnamespaces:\n  - dev\n  - staging\n```\n\nThis can then be installed via:\n\n```shell\ntelepresence helm install --namespace staging -f ./values.yaml\n```\n\n### Namespace collision detection\n\nThe Telepresence Helm chart incorporates a mechanism to prevent conflicts between Traffic Managers operating within\ndifferent namespaces. This is achieved by:\n1. Determining the Traffic Manager's set of namespaces by applying its namespace selector to all of the cluster's namespaces.\n2. Verifying that there is no overlap between the sets of namespaces for any pair of Traffic Managers.\n\nSo, for example, suppose you install one Traffic Manager to manage namespaces `dev` and `staging`, as:\n\n```bash\ntelepresence helm install --namespace dev --set 'namespaces={dev,staging}'\n```\n\nYou might then attempt to install another Traffic Manager to manage namespaces `staging` and `prod`:\n\n```bash\ntelepresence helm install --namespace prod --set 'namespaces={staging,prod}'\n```\n\nThis would fail with an error:\n\n```\ntelepresence helm install: error: execution error at (telepresence-oss/templates/agentInjectorWebhook.yaml:61:14): traffic-manager in namespace dev already manages namespace staging\n```\n\nTo fix this error, fix the overlap either by removing `staging` from the first install, or from the second.\n\n### Static versus Dynamic Namespace Selection\n\nA namespace selector can be dynamic or static. This in turn controls if telepresence needs \"cluster-wide\" or\n\"namespaced\" role/rolebinding pairs. A Traffic Manager configured with a dynamic selector requires cluster-wide\nnamespace access and `ClusterRole`/`ClusterRoleBinding` pairs. A Traffic Manager configured with a static selector needs\na `Role`/`RoleBinding` pair in each of the selected namespaces.\n\nA selector is considered _static_ if it meets the following conditions:\n- The selector must have exactly one element in either the `matchLabels` or the `matchExpression` list (a `key=value`\n  element in the `matchLabels` list, it is normalized into a `key in [value]` expression element).\n- The element must meet the following criteria:\n  The `key` of the match expression must be \"kubernetes.io/metadata.name\".\n  The `operator` of the match expression must be \"In\" (case sensitive).\n  The `values` list of the match expression must contain at least one value.\n\n## Static Namespace Selection RBAC\n\nOptionally, you can also configure user rbac to be scoped to the same namespaces as the manager itself.\nYou might want to do this if you don't give your users permissions throughout the cluster, and want to make sure they\nonly have the minimum set required to perform telepresence commands on certain namespaces.\n\nContinuing with the `dev` and `staging` example from the previous section, simply add the following to `values.yaml`\n(make sure you set the `subjects`!):\n\n```yaml\nclientRbac:\n  create: true\n\n  # These are the users or groups to which the user rbac will be bound.\n  # This MUST be set.\n  subjects: {}\n  # - kind: User\n  #   name: jane\n  #   apiGroup: rbac.authorization.k8s.io\n\n  # The namespaces can be explicitly specified here, but can be omitted unless the\n  # Traffic Manager's namespaceSelector is dynamic.\n  namespaces:\n  - dev\n  - staging\n```\n\n### Installing RBAC only\n\nTelepresence Traffic Manager does require some [RBAC](../reference/rbac.md) for the traffic-manager deployment itself, as well as for users.\nTo make it easier for operators to introspect / manage RBAC separately, you can use `rbac.only=true` to\nonly create the rbac-related objects.\nAdditionally, you can use `clientRbac.create=true` and `managerRbac.create=true` to toggle which subset(s) of RBAC objects you wish to create.\n\n## Install with Helm\n\nBefore you begin, you must ensure that the [helm command](https://helm.sh/docs/intro/install/) is installed.\n\n### Recommended version\nIn general, we recommend that you use a helm binary with a minor version that is less than three numbers below the version\nembedded in the telepresence binary. E.g., for embedded version 3.18.x, use helm >= 3.16.x.\n\nYou can check your helm version using:\n```bash\nhelm version\n```\nand the Telepresence embedded helm version using:\n```bash\ntelepresence helm version\n```\n\nThe Telepresence Helm chart is published at GitHub in the ghcr.io repository.\n\n### Installing\n\nInstall the latest stable version of the traffic-manager into the default \"ambassador\" namespace with the following command:\n\n```bash\nhelm install --create-namespace --namespace ambassador traffic-manager oci://ghcr.io/telepresenceio/telepresence-oss\n```\n\n### Upgrading/Downgrading\n\nUse this command if you installed the Traffic Manager into the \"ambassador\" namespace, and you just wish to upgrade it\nto the latest version without changing any configuration values:\n\n```bash\nhelm upgrade --namespace ambassador --reuse-values traffic-manager oci://ghcr.io/telepresenceio/telepresence-oss\n```\n\nIf you want to upgrade (or downgrade) the Traffic Manager to a specific version, add a `--version` flag with the version\nnumber to the upgrade command, e.g.: `--version v2.20.3`.\n\n### Uninstalling\n\nUse the following command to uninstall the Traffic Manager:\n```bash\nhelm uninstall --namespace ambassador traffic-manager\n```\n"
  },
  {
    "path": "docs/install/upgrade.md",
    "content": "---\ntitle: Upgrade client\ndescription: \"How to upgrade your installation of Telepresence and install previous versions.\"\nhide_table_of_contents: true\n---\n\nimport Platform from '@site/src/components/Platform';\n\n# Upgrade Process\nThe Telepresence CLI will periodically check for new versions and notify you when an upgrade is available. Running the same commands used for installation will replace your current version with the latest.\n\nBefore upgrading your CLI, you must stop any live Telepresence processes by issuing `telepresence quit -s` (or `telepresence quit -ur`\nif your current version is less than 2.8.0).\n\n<Platform.Provider>\n<Platform.TabGroup>\n<Platform.MacOSTab>\n\n## Upgrade with the package installer (Recommended)\n\nDownload and install the latest `.pkg` for your architecture from the [install page](client.md). The installer\nwill replace the previous version and restart the root daemon service.\n\n## OR upgrade with Homebrew\n\n```shell\nbrew upgrade telepresenceio/telepresence/telepresence-oss\n```\n\n## OR upgrade by downloading the binary manually\n\n### Intel Macs\n\n```shell\n# 1. Download the binary.\nsudo curl -fL https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-darwin-amd64 -o /usr/local/bin/telepresence\n\n# 2. Make the binary executable:\nsudo chmod a+x /usr/local/bin/telepresence\n```\n\n### Apple Silicon Macs\n\n```shell\n# 1. Ensure that no old binary exists. This is very important because Apple Silicon macs track the executable's\n# signature and just updating it in place will not work.\nsudo rm -f /usr/local/bin/telepresence\n\n# 2. Download the binary.\nsudo curl -fL https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-darwin-arm64 -o /usr/local/bin/telepresence\n\n# 3. Make the binary executable:\nsudo chmod a+x /usr/local/bin/telepresence\n```\n</Platform.MacOSTab>\n<Platform.GNULinuxTab>\n\n## Upgrade with a package manager (Recommended)\n\nDownload and install the latest `.deb` or `.rpm` package using the same commands as the initial\n[installation](client.md). The package manager will handle replacing the previous version and restarting the\nroot daemon service.\n\n## OR upgrade by downloading the binary manually\n\n```shell\n# 1. Download the latest binary (~95 MB):\n### AMD64\nsudo curl -fL https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-linux-amd64 -o /usr/local/bin/telepresence\n\n### ARM64\nsudo curl -fL https://github.com/telepresenceio/telepresence/releases/latest/download/telepresence-linux-arm64 -o /usr/local/bin/telepresence\n\n# 2. Make the binary executable:\nsudo chmod a+x /usr/local/bin/telepresence\n```\n\n</Platform.GNULinuxTab>\n<Platform.WindowsTab>\n\n## Upgrade with the setup installer (Recommended)\n\nDownload and run the latest setup installer from the [install page](client.md). The installer will replace the\nprevious version and restart the root daemon service.\n\n## OR upgrade by downloading manually\n\nDownload the latest zip from the [install page](client.md) and follow the manual installation steps, which will\nreplace the existing installation.\n\n</Platform.WindowsTab>\n</Platform.TabGroup>\n</Platform.Provider>\n\nThe Telepresence CLI contains an embedded Helm chart. See [Install/Uninstall the Traffic Manager](manager.md) if you want to also upgrade\nthe Traffic Manager in your cluster.\n"
  },
  {
    "path": "docs/licenses.md",
    "content": "Telepresence CLI incorporates Free and Open Source software under the following licenses:\n\n* [2-clause BSD license](https://opensource.org/licenses/BSD-2-Clause)\n* [3-clause BSD license](https://opensource.org/licenses/BSD-3-Clause)\n* [Apache License 2.0](https://opensource.org/licenses/Apache-2.0)\n* [ISC license](https://opensource.org/licenses/ISC)\n* [MIT No Attribution license](https://spdx.org/licenses/MIT-0.html)\n* [MIT license](https://opensource.org/licenses/MIT)\n* [Mozilla Public License 2.0](https://opensource.org/licenses/MPL-2.0)\n"
  },
  {
    "path": "docs/quick-start.md",
    "content": "---\ntitle: Quick start\ndescription: \"Start using Telepresence in your own environment. Follow these steps to intercept your service in your cluster.\"\nhide_table_of_contents: true\n---\n\n# Telepresence Quickstart\n\nTelepresence is an open source tool that enables you to set up remote development environments for Kubernetes where you can still use all of your favorite local tools like IDEs, debuggers, and profilers.\n\n## Prerequisites\n\n- [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/), the Kubernetes command-line tool, or the OpenShift Container Platform command-line interface, [oc](https://docs.openshift.com/container-platform/4.2/cli_reference/openshift_cli/getting-started-cli.html#cli-installing-cli_cli-developer-commands).\n- A Kubernetes Deployment and Service.\n\n## Install Telepresence\n\nFollow [Install Client](install/client.md) and [Install Traffic Manager](install/manager.md) instructions to install the\ntelepresence client on your workstation, and the traffic manager in your cluster.\n\nCheckout the [Howto](howtos/engage.md) to learn how Telepresence can engage with resources in your remote cluster,\nenabling you to run the code on your local workstation.\n\n## What’s Next?\n- [Learn about the Telepresence architecture.](reference/architecture)\n"
  },
  {
    "path": "docs/redirects.yml",
    "content": "- {from: \"\", to: \"quick-start\"}\n"
  },
  {
    "path": "docs/reference/architecture.md",
    "content": "---\ntitle: Architecture\ndescription: How Telepresence works to intercept traffic from your Kubernetes cluster to code running on your laptop.\nhide_table_of_contents: true\n---\n\n# Telepresence Architecture\n\n![Architecture](../images/TP_Architecture.svg)\n\n## Telepresence CLI\n\nThe Telepresence CLI orchestrates the moving parts on the workstation: it starts the Telepresence Daemons and then acts\nas a user-friendly interface to the Telepresence User Daemon.\n\n## Telepresence Daemons\nTelepresence has Daemons that run on a developer's workstation and act as the main point of communication to the cluster's\nnetwork in order to communicate with the cluster and handle intercepted traffic.\n\n### User-Daemon\nThe User-Daemon coordinates the creation and deletion of replacements, ingests and intercepts by communicating with the [Traffic Manager](#traffic-manager).\nAll requests from and to the cluster go through this Daemon.\n\n### Root-Daemon\nThe Root-Daemon manages the networking necessary to handle traffic between the local workstation and the cluster by setting up a\n[Virtual Network Device](tun-device.md) (VIF).\n\n## Traffic Manager\n\nThe Traffic Manager is the central point of communication between Traffic Agents in the cluster and Telepresence Daemons\non developer workstations. It is responsible for injecting the Traffic Agent sidecar into engaged pods,\nproxying all relevant inbound and outbound traffic, and tracking active engagements.\n\nThe Traffic-Manager is installed by a cluster administrator. It can either be installed using the Helm chart embedded\nin the telepresence client binary (`telepresence helm install`) or by using a Helm Chart directly.\n\n## Traffic Agent\n\nThe Traffic Agent is a sidecar container that facilitates engagements. When a `replace`, `ingest`, `intercept`, or `wiretap` is first\nstarted, the Traffic Agent container is injected into the workload's pod(s). You can see the Traffic Agent's status by\nrunning `telepresence list` or `kubectl describe pod <pod-name>`.\n\nDepending on if an `replace` or `intercept` is active or not, the Traffic Agent will either route the incoming request \nto your workstation, or it will pass it along to the container in the pod usually handling requests.\n\nWhen a `wiretap` is active, the Traffic Agent will send a copy of the incoming requests to your workstation.\n\nPlease see [Traffic Agent Sidecar](engagements/sidecar.md) for details."
  },
  {
    "path": "docs/reference/cli/telepresence.md",
    "content": "---\ntitle: telepresence\ndescription: Connect your workstation to a Kubernetes cluster\nhide_table_of_contents: true\n---\n\nConnect your workstation to a Kubernetes cluster\n\n## Synopsis:\n\nTelepresence can connect to a cluster and route all outbound traffic from your\nworkstation to that cluster so that software running locally can communicate\nas if it executed remotely, inside the cluster. This is achieved using the\ncommand:\n```bash\ntelepresence connect\n```\n\nTelepresence can also intercept traffic intended for a specific service in a\ncluster and redirect it to your local workstation:\n\n```bash\ntelepresence intercept &lt;name of service&gt;\n```\n\nTelepresence uses a background process, the user daemon, to manage the cluster\nsession, and a system service, the root daemon, to modify the workstation's\nnetwork and DNS so that the cluster's services become available locally.\n\nIf Telepresence was installed as a standalone binary, the system service will\nnot be present. A root daemon must then be started using sudo, which may\nresult in a password prompt.\n\n### Usage:\n```\n  telepresence [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [completion](telepresence_completion) | Generate a shell completion script |\n| [compose](telepresence_compose) | Define and run multi-container applications with Telepresence and Docker |\n| [config](telepresence_config) | Telepresence configuration commands |\n| [connect](telepresence_connect) | Connect to a cluster |\n| [curl](telepresence_curl) | curl with daemon network |\n| [docker-run](telepresence_docker-run) | Docker run with daemon network |\n| [gather-logs](telepresence_gather-logs) | Gather logs from traffic-manager, traffic-agent, user and root daemons, and export them into a zip file. |\n| [genyaml](telepresence_genyaml) | Generate YAML for use in kubernetes manifests. |\n| [helm](telepresence_helm) | Helm commands using the embedded Telepresence Helm chart. |\n| [ingest](telepresence_ingest) | Ingest a container |\n| [intercept](telepresence_intercept) | Intercept a service |\n| [leave](telepresence_leave) | Remove existing intercept |\n| [list](telepresence_list) | List current intercepts |\n| [list-contexts](telepresence_list-contexts) | Show all contexts |\n| [list-namespaces](telepresence_list-namespaces) | Show all namespaces |\n| [loglevel](telepresence_loglevel) | Temporarily change the log-level of the traffic-manager, traffic-agent, and user and root daemons |\n| [mcp](telepresence_mcp) | MCP server management |\n| [quit](telepresence_quit) | Tell telepresence daemons to quit |\n| [replace](telepresence_replace) | Replace a container |\n| [revoke](telepresence_revoke) | Revoke an intercept by intercept ID. The intercept ID must be in the format &lt;session_id&gt;:&lt;intercept_name&gt; |\n| [serve](telepresence_serve) | Start the browser on a remote service |\n| [status](telepresence_status) | Show connectivity status |\n| [uninstall](telepresence_uninstall) | Uninstall telepresence agents |\n| [version](telepresence_version) | Show version |\n| [wiretap](telepresence_wiretap) | Wiretap a Service |\n\n### Flags:\n```\n  -h, --help   help for telepresence\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_completion.md",
    "content": "---\ntitle: telepresence completion\ndescription: Generate a shell completion script\nhide_table_of_contents: true\n---\n\nGenerate a shell completion script\n\n## Synopsis:\n\nTo load completions:\n\n### Bash:\n```bash\n  $ source &lt;(telepresence completion bash)\n\n  # To load completions for each session, execute once:\n  # Linux:\n  $ telepresence completion bash &gt; /etc/bash_completion.d/telepresence\n  # macOS:\n  $ telepresence completion bash &gt; $(brew --prefix)/etc/bash_completion.d/telepresence\n```\n\n### Zsh:\n```zsh\n\n  # If shell completion is not already enabled in your environment,\n  # you will need to enable it.  You can execute the following once:\n\n  $ echo &quot;autoload -U compinit; compinit&quot; &gt;&gt; ~/.zshrc\n\n  # To load completions for each session, execute once:\n  $ telepresence completion zsh &gt; &quot;${fpath[1]}/_telepresence&quot;\n\n  # You will need to start a new shell for this setup to take effect.\n```\n\n### fish:\n```fish\n\n  $ telepresence completion fish | source\n\n  # To load completions for each session, execute once:\n  $ telepresence completion fish &gt; ~/.config/fish/completions/telepresence.fish\n```\n\n### PowerShell:\n```powershell\n\n  PS&gt; telepresence completion powershell | Out-String | Invoke-Expression\n\n  # To load completions for every new session, run:\n  PS&gt; telepresence completion powershell &gt; telepresence.ps1\n  # and source this file from your PowerShell profile.\n```\n\n### Usage:\n```\n  telepresence completion [flags]\n```\n\n### Flags:\n```\n  -h, --help   help for completion\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose.md",
    "content": "---\ntitle: telepresence compose\ndescription: Define and run multi-container applications with Telepresence and Docker\nhide_table_of_contents: true\n---\n\nDefine and run multi-container applications with Telepresence and Docker\n\n### Usage:\n```\n  telepresence compose [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [attach](telepresence_compose_attach) | Attach local standard input, output, and error streams to a service's running container |\n| [bridge](telepresence_compose_bridge) | Convert compose files into another model |\n| [build](telepresence_compose_build) | Build or rebuild services |\n| [commit](telepresence_compose_commit) | Create a new image from a service container's changes |\n| [config](telepresence_compose_config) | Parse, resolve and render compose file in canonical format |\n| [cp](telepresence_compose_cp) | docker compose cp [OPTIONS] SRC_PATH|- SERVICE:DEST_PATH |\n| [create](telepresence_compose_create) | Creates containers for a service |\n| [down](telepresence_compose_down) | Stop and remove containers, networks |\n| [events](telepresence_compose_events) | Receive real time events from containers |\n| [exec](telepresence_compose_exec) | Execute a command in a running container |\n| [export](telepresence_compose_export) | Export a service container's filesystem as a tar archive |\n| [images](telepresence_compose_images) | List images used by the created containers |\n| [kill](telepresence_compose_kill) | Force stop service containers |\n| [logs](telepresence_compose_logs) | View output from containers |\n| [ls](telepresence_compose_ls) | List running compose projects |\n| [pause](telepresence_compose_pause) | Pause services |\n| [port](telepresence_compose_port) | Print the public port for a port binding |\n| [ps](telepresence_compose_ps) | List containers |\n| [publish](telepresence_compose_publish) | Publish compose application |\n| [pull](telepresence_compose_pull) | Pull service images |\n| [push](telepresence_compose_push) | Push service images |\n| [restart](telepresence_compose_restart) | Restart service containers |\n| [rm](telepresence_compose_rm) | Removes stopped service containers |\n| [run](telepresence_compose_run) | Run a one-off command on a service |\n| [scale](telepresence_compose_scale) | Scale services |\n| [start](telepresence_compose_start) | Start services |\n| [stats](telepresence_compose_stats) | Display a live stream of container(s) resource usage statistics |\n| [stop](telepresence_compose_stop) | Stop services |\n| [top](telepresence_compose_top) | Display the running processes |\n| [unpause](telepresence_compose_unpause) | Unpause services |\n| [up](telepresence_compose_up) | Create and start containers |\n| [version](telepresence_compose_version) | Show the Docker Compose version information |\n| [volumes](telepresence_compose_volumes) | List volumes |\n| [wait](telepresence_compose_wait) | Block until containers of all (or specified) services stop. |\n| [watch](telepresence_compose_watch) | Watch build context for service and rebuild/refresh containers when files are updated |\n\n### Flags:\n```\n  -h, --help   help for compose\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence compose [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_attach.md",
    "content": "---\ntitle: telepresence compose attach\ndescription: Attach local standard input, output, and error streams to a service's running container\nhide_table_of_contents: true\n---\n\nAttach local standard input, output, and error streams to a service's running container\n\n### Usage:\n```\n  telepresence compose attach [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for attach\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose attach flags:\n```\n      --detach-keys string   Override the key sequence for detaching from a container.\n      --index int            index of the container if service has multiple replicas.\n      --no-stdin             Do not attach STDIN\n      --sig-proxy            Proxy all received signals to the process (default true)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_bridge.md",
    "content": "---\ntitle: telepresence compose bridge\ndescription: Convert compose files into another model\nhide_table_of_contents: true\n---\n\nConvert compose files into another model\n\n### Usage:\n```\n  telepresence compose bridge [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for bridge\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose bridge flags:\n```\n\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_build.md",
    "content": "---\ntitle: telepresence compose build\ndescription: Build or rebuild services\nhide_table_of_contents: true\n---\n\nBuild or rebuild services\n\n### Usage:\n```\n  telepresence compose build [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for build\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose build flags:\n```\n      --build-arg stringArray   Set build-time variables for services\n      --builder string          Set builder to use\n      --check                   Check build configuration\n  -m, --memory bytes            Set memory limit for the build container. Not supported by BuildKit.\n      --no-cache                Do not use cache when building the image\n      --print                   Print equivalent bake file\n      --provenance string       Add a provenance attestation\n      --pull                    Always attempt to pull a newer version of the image\n      --push                    Push service images\n  -q, --quiet                   Suppress the build output\n      --sbom string             Add a SBOM attestation\n      --ssh string              Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)\n      --with-dependencies       Also build dependencies (transitively)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_commit.md",
    "content": "---\ntitle: telepresence compose commit\ndescription: Create a new image from a service container's changes\nhide_table_of_contents: true\n---\n\nCreate a new image from a service container's changes\n\n### Usage:\n```\n  telepresence compose commit [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for commit\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose commit flags:\n```\n  -a, --author string    Author (e.g., &quot;John Hannibal Smith &lt;hannibal@a-team.com&gt;&quot;)\n  -c, --change list      Apply Dockerfile instruction to the created image\n      --index int        index of the container if service has multiple replicas.\n  -m, --message string   Commit message\n  -p, --pause            Pause container during commit (default true)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_config.md",
    "content": "---\ntitle: telepresence compose config\ndescription: Parse, resolve and render compose file in canonical format\nhide_table_of_contents: true\n---\n\nParse, resolve and render compose file in canonical format\n\n### Usage:\n```\n  telepresence compose config [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for config\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose config flags:\n```\n      --environment             Print environment used for interpolation.\n      --format string           Format the output. Values: [yaml | json]\n      --hash string             Print the service config hash, one per line.\n      --images                  Print the image names, one per line.\n      --lock-image-digests      Produces an override file with image digests\n      --models                  Print the model names, one per line.\n      --networks                Print the network names, one per line.\n      --no-consistency          Don't check model consistency - warning: may produce invalid Compose output\n      --no-env-resolution       Don't resolve service env files\n      --no-interpolate          Don't interpolate environment variables\n      --no-normalize            Don't normalize compose model\n      --no-path-resolution      Don't resolve file paths\n  -o, --output string           Save to file (default to stdout)\n      --profiles                Print the profile names, one per line.\n  -q, --quiet                   Only validate the configuration, don't print anything\n      --resolve-image-digests   Pin image tags to digests\n      --services                Print the service names, one per line.\n      --variables               Print model variables and default values.\n      --volumes                 Print the volume names, one per line.\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_cp.md",
    "content": "---\ntitle: telepresence compose cp\ndescription: docker compose cp [OPTIONS] SRC_PATH|- SERVICE:DEST_PATH\nhide_table_of_contents: true\n---\n\ndocker compose cp [OPTIONS] SRC_PATH|- SERVICE:DEST_PATH\n\n### Usage:\n```\n  telepresence compose cp [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for cp\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose cp flags:\n```\n      --all           Include containers created by the run command\n  -a, --archive       Archive mode (copy all uid/gid information)\n  -L, --follow-link   Always follow symbol link in SRC_PATH\n      --index int     Index of the container if service has multiple replicas\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_create.md",
    "content": "---\ntitle: telepresence compose create\ndescription: Creates containers for a service\nhide_table_of_contents: true\n---\n\nCreates containers for a service\n\n### Usage:\n```\n  telepresence compose create [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for create\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose create flags:\n```\n      --build            Build images before starting containers\n      --force-recreate   Recreate containers even if their configuration and image haven't changed\n      --no-build         Don't build an image, even if it's policy\n      --no-recreate      If containers already exist, don't recreate them. Incompatible with --force-recreate.\n      --pull string      Pull image before running (&quot;always&quot;|&quot;missing&quot;|&quot;never&quot;|&quot;build&quot;) (default &quot;policy&quot;)\n      --quiet-pull       Pull without printing progress information\n      --remove-orphans   Remove containers for services not defined in the Compose file\n      --scale uint32     Scale SERVICE to NUM instances. Overrides the scale setting in the Compose file if present.\n  -y, --yes              Assume &quot;yes&quot; as answer to all prompts and run non-interactively\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_down.md",
    "content": "---\ntitle: telepresence compose down\ndescription: Stop and remove containers, networks\nhide_table_of_contents: true\n---\n\nStop and remove containers, networks\n\n### Usage:\n```\n  telepresence compose down [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for down\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose down flags:\n```\n      --remove-orphans   Remove containers for services not defined in the Compose file\n      --rmi string       Remove images used by services. &quot;local&quot; remove only images that don't have a custom tag (&quot;local&quot;|&quot;all&quot;)\n  -t, --timeout int      Specify a shutdown timeout in seconds\n  -v, --volumes          Remove named volumes declared in the &quot;volumes&quot; section of the Compose file and anonymous volumes attached to containers\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_events.md",
    "content": "---\ntitle: telepresence compose events\ndescription: Receive real time events from containers\nhide_table_of_contents: true\n---\n\nReceive real time events from containers\n\n### Usage:\n```\n  telepresence compose events [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for events\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose events flags:\n```\n      --json           Output events as a stream of json objects\n      --since string   Show all events created since timestamp\n      --until string   Stream events until this timestamp\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_exec.md",
    "content": "---\ntitle: telepresence compose exec\ndescription: Execute a command in a running container\nhide_table_of_contents: true\n---\n\nExecute a command in a running container\n\n### Usage:\n```\n  telepresence compose exec [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for exec\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose exec flags:\n```\n  -d, --detach            Detached mode: Run command in the background\n  -e, --env stringArray   Set environment variables\n      --index int         Index of the container if service has multiple replicas\n  -T, --no-tty            Disable pseudo-TTY allocation. By default 'docker compose exec' allocates a TTY. (default true)\n      --privileged        Give extended privileges to the process\n  -u, --user string       Run the command as this user\n  -w, --workdir string    Path to workdir directory for this command\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_export.md",
    "content": "---\ntitle: telepresence compose export\ndescription: Export a service container's filesystem as a tar archive\nhide_table_of_contents: true\n---\n\nExport a service container's filesystem as a tar archive\n\n### Usage:\n```\n  telepresence compose export [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for export\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose export flags:\n```\n      --index int       index of the container if service has multiple replicas.\n  -o, --output string   Write to a file, instead of STDOUT\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_images.md",
    "content": "---\ntitle: telepresence compose images\ndescription: List images used by the created containers\nhide_table_of_contents: true\n---\n\nList images used by the created containers\n\n### Usage:\n```\n  telepresence compose images [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for images\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose images flags:\n```\n      --format string   Format the output. Values: [table | json] (default &quot;table&quot;)\n  -q, --quiet           Only display IDs\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_kill.md",
    "content": "---\ntitle: telepresence compose kill\ndescription: Force stop service containers\nhide_table_of_contents: true\n---\n\nForce stop service containers\n\n### Usage:\n```\n  telepresence compose kill [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for kill\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose kill flags:\n```\n      --remove-orphans   Remove containers for services not defined in the Compose file\n  -s, --signal string    SIGNAL to send to the container (default &quot;\\&quot;SIGKILL\\&quot;&quot;)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_logs.md",
    "content": "---\ntitle: telepresence compose logs\ndescription: View output from containers\nhide_table_of_contents: true\n---\n\nView output from containers\n\n### Usage:\n```\n  telepresence compose logs [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for logs\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose logs flags:\n```\n  -f, --follow          Follow log output\n      --index int       index of the container if service has multiple replicas\n      --no-color        Produce monochrome output\n      --no-log-prefix   Don't print prefix in logs\n      --since string    Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)\n  -n, --tail string     Number of lines to show from the end of the logs for each container (default &quot;all&quot;)\n  -t, --timestamps      Show timestamps\n      --until string    Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_ls.md",
    "content": "---\ntitle: telepresence compose ls\ndescription: List running compose projects\nhide_table_of_contents: true\n---\n\nList running compose projects\n\n### Usage:\n```\n  telepresence compose ls [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for ls\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose ls flags:\n```\n  -a, --all             Show all stopped Compose projects\n      --filter string   Filter output based on conditions provided\n      --format string   Format the output. Values: [table | json] (default &quot;table&quot;)\n  -q, --quiet           Only display project names\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_pause.md",
    "content": "---\ntitle: telepresence compose pause\ndescription: Pause services\nhide_table_of_contents: true\n---\n\nPause services\n\n### Usage:\n```\n  telepresence compose pause [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for pause\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose pause flags:\n```\n\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_port.md",
    "content": "---\ntitle: telepresence compose port\ndescription: Print the public port for a port binding\nhide_table_of_contents: true\n---\n\nPrint the public port for a port binding\n\n### Usage:\n```\n  telepresence compose port [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for port\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose port flags:\n```\n      --index int         Index of the container if service has multiple replicas\n      --protocol string   tcp or udp (default &quot;\\&quot;tcp\\&quot;&quot;)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_ps.md",
    "content": "---\ntitle: telepresence compose ps\ndescription: List containers\nhide_table_of_contents: true\n---\n\nList containers\n\n### Usage:\n```\n  telepresence compose ps [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for ps\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose ps flags:\n```\n  -a, --all                  Show all stopped containers (including those created by the run command)\n      --filter string        Filter services by a property (supported filters: status)\n      --format string        Format output using a custom template: 'table':            Print output in table format with column headers (default) 'table TEMPLATE':   Print output in table format using the given Go template 'json':             Print in JSON format 'TEMPLATE':         Print output using the given Go template. Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates (default &quot;table&quot;)\n      --no-trunc             Don't truncate output\n      --orphans              Include orphaned services (not declared by project) (default true)\n  -q, --quiet                Only display IDs\n      --services             Display services\n      --status stringArray   Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_publish.md",
    "content": "---\ntitle: telepresence compose publish\ndescription: Publish compose application\nhide_table_of_contents: true\n---\n\nPublish compose application\n\n### Usage:\n```\n  telepresence compose publish [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for publish\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose publish flags:\n```\n      --app                     Published compose application (includes referenced images)\n      --oci-version string      OCI image/artifact specification version (automatically determined by default)\n      --resolve-image-digests   Pin image tags to digests\n      --with-env                Include environment variables in the published OCI artifact\n  -y, --yes                     Assume &quot;yes&quot; as answer to all prompts\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_pull.md",
    "content": "---\ntitle: telepresence compose pull\ndescription: Pull service images\nhide_table_of_contents: true\n---\n\nPull service images\n\n### Usage:\n```\n  telepresence compose pull [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for pull\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose pull flags:\n```\n      --ignore-buildable       Ignore images that can be built\n      --ignore-pull-failures   Pull what it can and ignores images with pull failures\n      --include-deps           Also pull services declared as dependencies\n      --policy string          Apply pull policy (&quot;missing&quot;|&quot;always&quot;)\n  -q, --quiet                  Pull without printing progress information\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_push.md",
    "content": "---\ntitle: telepresence compose push\ndescription: Push service images\nhide_table_of_contents: true\n---\n\nPush service images\n\n### Usage:\n```\n  telepresence compose push [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for push\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose push flags:\n```\n      --ignore-push-failures   Push what it can and ignores images with push failures\n      --include-deps           Also push images of services declared as dependencies\n  -q, --quiet                  Push without printing progress information\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_restart.md",
    "content": "---\ntitle: telepresence compose restart\ndescription: Restart service containers\nhide_table_of_contents: true\n---\n\nRestart service containers\n\n### Usage:\n```\n  telepresence compose restart [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for restart\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose restart flags:\n```\n      --no-deps       Don't restart dependent services\n  -t, --timeout int   Specify a shutdown timeout in seconds\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_rm.md",
    "content": "---\ntitle: telepresence compose rm\ndescription: Removes stopped service containers\nhide_table_of_contents: true\n---\n\nRemoves stopped service containers\n\n### Usage:\n```\n  telepresence compose rm [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for rm\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose rm flags:\n```\n  -f, --force     Don't ask to confirm removal\n  -s, --stop      Stop the containers, if required, before removing\n  -v, --volumes   Remove any anonymous volumes attached to containers\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_run.md",
    "content": "---\ntitle: telepresence compose run\ndescription: Run a one-off command on a service\nhide_table_of_contents: true\n---\n\nRun a one-off command on a service\n\n### Usage:\n```\n  telepresence compose run [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for run\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose run flags:\n```\n      --build                       Build image before starting container\n      --cap-add list                Add Linux capabilities\n      --cap-drop list               Drop Linux capabilities\n  -d, --detach                      Run container in background and print container ID\n      --entrypoint string           Override the entrypoint of the image\n  -e, --env stringArray             Set environment variables\n      --env-from-file stringArray   Set environment variables from file\n  -i, --interactive                 Keep STDIN open even if not attached (default true)\n  -l, --label stringArray           Add or override a label\n      --name string                 Assign a name to the container\n  -T, --no-TTY                      Disable pseudo-TTY allocation (default: auto-detected) (default true)\n      --no-deps                     Don't start linked services\n  -p, --publish stringArray         Publish a container's port(s) to the host\n      --pull string                 Pull image before running (&quot;always&quot;|&quot;missing&quot;|&quot;never&quot;) (default &quot;policy&quot;)\n  -q, --quiet                       Don't print anything to STDOUT\n      --quiet-build                 Suppress progress output from the build process\n      --quiet-pull                  Pull without printing progress information\n      --remove-orphans              Remove containers for services not defined in the Compose file\n      --rm                          Automatically remove the container when it exits\n  -P, --service-ports               Run command with all service's ports enabled and mapped to the host\n      --use-aliases                 Use the service's network useAliases in the network(s) the container connects to\n  -u, --user string                 Run as specified username or uid\n  -v, --volume stringArray          Bind mount a volume\n  -w, --workdir string              Working directory inside the container\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_scale.md",
    "content": "---\ntitle: telepresence compose scale\ndescription: Scale services\nhide_table_of_contents: true\n---\n\nScale services\n\n### Usage:\n```\n  telepresence compose scale [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for scale\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose scale flags:\n```\n      --no-deps   Don't start linked services\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_start.md",
    "content": "---\ntitle: telepresence compose start\ndescription: Start services\nhide_table_of_contents: true\n---\n\nStart services\n\n### Usage:\n```\n  telepresence compose start [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for start\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose start flags:\n```\n      --wait               Wait for services to be running|healthy. Implies detached mode.\n      --wait-timeout int   Maximum duration in seconds to wait for the project to be running|healthy\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_stats.md",
    "content": "---\ntitle: telepresence compose stats\ndescription: Display a live stream of container(s) resource usage statistics\nhide_table_of_contents: true\n---\n\nDisplay a live stream of container(s) resource usage statistics\n\n### Usage:\n```\n  telepresence compose stats [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for stats\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose stats flags:\n```\n  -a, --all             Show all containers (default shows just running)\n      --format string   Format output using a custom template: 'table':            Print output in table format with column headers (default) 'table TEMPLATE':   Print output in table format using the given Go template 'json':             Print in JSON format 'TEMPLATE':         Print output using the given Go template. Refer to https://docs.docker.com/engine/cli/formatting/ for more information about formatting output with templates\n      --no-stream       Disable streaming stats and only pull the first result\n      --no-trunc        Do not truncate output\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_stop.md",
    "content": "---\ntitle: telepresence compose stop\ndescription: Stop services\nhide_table_of_contents: true\n---\n\nStop services\n\n### Usage:\n```\n  telepresence compose stop [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for stop\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose stop flags:\n```\n  -t, --timeout int   Specify a shutdown timeout in seconds\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_top.md",
    "content": "---\ntitle: telepresence compose top\ndescription: Display the running processes\nhide_table_of_contents: true\n---\n\nDisplay the running processes\n\n### Usage:\n```\n  telepresence compose top [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for top\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose top flags:\n```\n\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_unpause.md",
    "content": "---\ntitle: telepresence compose unpause\ndescription: Unpause services\nhide_table_of_contents: true\n---\n\nUnpause services\n\n### Usage:\n```\n  telepresence compose unpause [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for unpause\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose unpause flags:\n```\n\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_up.md",
    "content": "---\ntitle: telepresence compose up\ndescription: Create and start containers\nhide_table_of_contents: true\n---\n\nCreate and start containers\n\n### Usage:\n```\n  telepresence compose up [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for up\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose up flags:\n```\n      --abort-on-container-exit      Stops all containers if any container was stopped. Incompatible with -d\n      --abort-on-container-failure   Stops all containers if any container exited with failure. Incompatible with -d\n      --always-recreate-deps         Recreate dependent containers. Incompatible with --no-recreate.\n      --attach stringArray           Restrict attaching to the specified services. Incompatible with --attach-dependencies.\n      --attach-dependencies          Automatically attach to log output of dependent services\n      --build                        Build images before starting containers\n  -d, --detach                       Detached mode: Run containers in the background\n      --exit-code-from string        Return the exit code of the selected service container. Implies --abort-on-container-exit\n      --force-recreate               Recreate containers even if their configuration and image haven't changed\n      --menu                         Enable interactive shortcuts when running attached. Incompatible with --detach. Can also be enable/disable by setting COMPOSE_MENU environment var.\n      --no-attach stringArray        Do not attach (stream logs) to the specified services\n      --no-build                     Don't build an image, even if it's policy\n      --no-color                     Produce monochrome output\n      --no-deps                      Don't start linked services\n      --no-log-prefix                Don't print prefix in logs\n      --no-recreate                  If containers already exist, don't recreate them. Incompatible with --force-recreate.\n      --no-start                     Don't start the services after creating them\n      --pull string                  Pull image before running (&quot;always&quot;|&quot;missing&quot;|&quot;never&quot;) (default &quot;policy&quot;)\n      --quiet-build                  Suppress the build output\n      --quiet-pull                   Pull without printing progress information\n      --remove-orphans               Remove containers for services not defined in the Compose file\n  -V, --renew-anon-volumes           Recreate anonymous volumes instead of retrieving data from the previous containers\n      --scale uint32                 Scale SERVICE to NUM instances. Overrides the scale setting in the Compose file if present.\n  -t, --timeout int                  Use this timeout in seconds for container shutdown when attached or when containers are already running\n      --timestamps                   Show timestamps\n      --wait                         Wait for services to be running|healthy. Implies detached mode.\n      --wait-timeout int             Maximum duration in seconds to wait for the project to be running|healthy\n  -w, --watch                        Watch source code and rebuild/refresh containers when files are updated.\n  -y, --yes                          Assume &quot;yes&quot; as answer to all prompts and run non-interactively\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_version.md",
    "content": "---\ntitle: telepresence compose version\ndescription: Show the Docker Compose version information\nhide_table_of_contents: true\n---\n\nShow the Docker Compose version information\n\n### Usage:\n```\n  telepresence compose version [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for version\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose version flags:\n```\n  -f, --format string   Format the output. Values: [pretty | json]. (Default: pretty)\n      --short           Shows only Compose's version number\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_volumes.md",
    "content": "---\ntitle: telepresence compose volumes\ndescription: List volumes\nhide_table_of_contents: true\n---\n\nList volumes\n\n### Usage:\n```\n  telepresence compose volumes [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for volumes\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose volumes flags:\n```\n      --format string   Format output using a custom template: 'table':            Print output in table format with column headers (default) 'table TEMPLATE':   Print output in table format using the given Go template 'json':             Print in JSON format 'TEMPLATE':         Print output using the given Go template. Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates (default &quot;table&quot;)\n  -q, --quiet           Only display volume names\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_wait.md",
    "content": "---\ntitle: telepresence compose wait\ndescription: Block until containers of all (or specified) services stop.\nhide_table_of_contents: true\n---\n\nBlock until containers of all (or specified) services stop.\n\n### Usage:\n```\n  telepresence compose wait [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for wait\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose wait flags:\n```\n      --down-project   Drops project when the first container stops\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_compose_watch.md",
    "content": "---\ntitle: telepresence compose watch\ndescription: Watch build context for service and rebuild/refresh containers when files are updated\nhide_table_of_contents: true\n---\n\nWatch build context for service and rebuild/refresh containers when files are updated\n\n### Usage:\n```\n  telepresence compose watch [flags] [services]\n```\n\n### Flags:\n```\n  -h, --help   help for watch\n```\n\n### Compose flags:\n```\n      --env-file stringArray       Optional environment files\n  -f, --file stringArray           Compose configuration files\n      --profile stringArray        Profile to enable\n      --project-directory string   Specify an alternate working directory (default: the path of the, first specified, Compose file)\n      --project-name string        Project name\n```\n\n### Compose watch flags:\n```\n      --no-up   Do not build &amp; start services before watching\n      --prune   Prune dangling images on rebuild (default true)\n      --quiet   hide build output\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_config.md",
    "content": "---\ntitle: telepresence config\ndescription: Telepresence configuration commands\nhide_table_of_contents: true\n---\n\nTelepresence configuration commands\n\n### Usage:\n```\n  telepresence config [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [view](telepresence_config_view) | View current Telepresence configuration |\n\n### Flags:\n```\n  -h, --help   help for config\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence config [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_config_view.md",
    "content": "---\ntitle: telepresence config view\ndescription: View current Telepresence configuration\nhide_table_of_contents: true\n---\n\nView current Telepresence configuration\n\n### Usage:\n```\n  telepresence config view [flags]\n```\n\n### Flags:\n```\n  -c, --client-only   Only view config from client file.\n  -h, --help          help for view\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_connect.md",
    "content": "---\ntitle: telepresence connect\ndescription: Connect to a cluster\nhide_table_of_contents: true\n---\n\nConnect to a cluster\n\n### Usage:\n```\n  telepresence connect [flags] [-- &lt;command to run while connected&gt;]\n```\n\n### Flags:\n```\n      --allow-conflicting-subnets strings   Comma separated list of CIDR that will be allowed to conflict with local subnets\n      --also-proxy strings                  Additional comma separated list of CIDR to proxy\n      --docker                              Start, or connect to, daemon in a docker container\n      --expose stringArray                  Port that a containerized daemon will expose. See docker run -p for more info. Can be repeated\n  -h, --help                                help for connect\n      --hostname string                     Hostname used by a containerized daemon\n      --manager-namespace string            The namespace where the traffic manager is to be found. Overrides any other manager namespace set in config\n      --mapped-namespaces strings           Comma separated list of namespaces considered by DNS resolver and NAT for outbound connections. Defaults to all namespaces\n      --name string                         Optional name to use for the connection\n  -n, --namespace string                    If present, the namespace scope for this CLI request\n      --never-proxy strings                 Comma separated list of CIDR to never proxy\n      --proxy-via strings                   Use Network Address Translation to create virtual IPs for the given CIDR, and route via WORKLOAD. Must be in the form CIDR=WORKLOAD. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n      --reroute-local strings               Reroute port on local host to remote host. Format is &lt;local port&gt;:&lt;host&gt;:&lt;port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --reroute-remote strings              Reroute port on remote host. Format is &lt;host&gt;:&lt;port&gt;:&lt;new port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --vnat strings                        Use Network Address Translation to create virtual IPs for the given CIDR. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_curl.md",
    "content": "---\ntitle: telepresence curl\ndescription: curl with daemon network\nhide_table_of_contents: true\n---\n\ncurl with daemon network\n\n### Usage:\n```\n  telepresence curl\n```\n\n### Flags:\n```\n  -h, --help   help for curl\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_docker-run.md",
    "content": "---\ntitle: telepresence docker-run\ndescription: Docker run with daemon network\nhide_table_of_contents: true\n---\n\nDocker run with daemon network\n\n### Usage:\n```\n  telepresence docker-run\n```\n\n### Flags:\n```\n  -h, --help   help for docker-run\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_gather-logs.md",
    "content": "---\ntitle: telepresence gather-logs\ndescription: Gather logs from traffic-manager, traffic-agent, user and root daemons, and export them into a zip file.\nhide_table_of_contents: true\n---\n\nGather logs from traffic-manager, traffic-agent, user and root daemons, and export them into a zip file.\n\n## Synopsis:\n\nGather logs from traffic-manager, traffic-agent, user and root daemons,\nand export them into a zip file. Useful if you are opening a Github issue or asking\nsomeone to help you debug Telepresence.\n\n### Usage:\n```\n  telepresence gather-logs [flags]\n```\n\n### Examples:\n```\nHere are a few examples of how you can use this command:\n# Get all logs and export to a given file\ntelepresence gather-logs -o /tmp/telepresence_logs.zip\n\n# Get all logs and pod yaml manifests for components in the kubernetes cluster\ntelepresence gather-logs -o /tmp/telepresence_logs.zip --get-pod-yaml\n\n# Get all logs for the daemons only\ntelepresence gather-logs --traffic-agents=None --traffic-manager=False\n\n# Get all logs for pods that have &quot;echo-easy&quot; in the name, useful if you have multiple replicas\ntelepresence gather-logs --traffic-manager=False --traffic-agents=echo-easy\n\n# Get all logs for a specific pod\ntelepresence gather-logs --traffic-manager=False --traffic-agents=echo-easy-6848967857-tw4jw\n\n# Get logs from everything except the daemons\ntelepresence gather-logs --daemons=None\n\n```\n\n### Flags:\n```\n  -a, --anonymize               To anonymize pod names + namespaces from the logs\n      --daemons string          Comma separated list of daemons you want logs from: all, root, user, kubeauth, None (default &quot;all&quot;)\n  -y, --get-pod-yaml            Get the yaml of any pods you are getting logs for\n  -h, --help                    help for gather-logs\n  -o, --output-file string      The file you want to output the logs to.\n      --traffic-agents string   Traffic-agents to collect logs from: all, name substring, None (default &quot;all&quot;)\n      --traffic-manager         If you want to collect logs from the traffic-manager (default true)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_genyaml.md",
    "content": "---\ntitle: telepresence genyaml\ndescription: Generate YAML for use in kubernetes manifests.\nhide_table_of_contents: true\n---\n\nGenerate YAML for use in kubernetes manifests.\n\n## Synopsis:\n\nGenerate traffic-agent yaml for use in kubernetes manifests.\nThis allows the traffic agent to be injected by hand into existing kubernetes manifests.\nFor your modified workload to be valid, you'll have to manually inject annotations, a\ncontainer, and a volume into the workload; you can do this by running &quot;genyaml config&quot;,\n&quot;genyaml container&quot;, &quot;genyaml initcontainer&quot;, &quot;genyaml annotations&quot;, and &quot;genyaml volume&quot;.\n\nNOTE: It is recommended that you not do this unless strictly necessary. Instead, we suggest letting\ntelepresence's webhook injector configure the traffic agents on demand.\n\n### Usage:\n```\n  telepresence genyaml [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [annotations](telepresence_genyaml_annotations) | Generate YAML for the pod template metadata annotations. |\n| [config](telepresence_genyaml_config) | Generate YAML for the agent's entry in the telepresence-agents configmap. |\n| [container](telepresence_genyaml_container) | Generate YAML for the traffic-agent container. |\n| [initcontainer](telepresence_genyaml_initcontainer) | Generate YAML for the traffic-agent init container. |\n| [volume](telepresence_genyaml_volume) | Generate YAML for the traffic-agent volume. |\n\n### Flags:\n```\n  -h, --help            help for genyaml\n  -o, --output string   Path to the file to place the output in. Defaults to '-' which means stdout. (default &quot;-&quot;)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence genyaml [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_genyaml_annotations.md",
    "content": "---\ntitle: telepresence genyaml annotations\ndescription: Generate YAML for the pod template metadata annotations.\nhide_table_of_contents: true\n---\n\nGenerate YAML for the pod template metadata annotations.\n\n## Synopsis:\n\nGenerate YAML for the pod template metadata annotations. See genyaml for more info on what this means\n\n### Usage:\n```\n  telepresence genyaml annotations [flags]\n```\n\n### Flags:\n```\n  -a, --agent string    Path to the yaml containing the generated agent config\n  -h, --help            help for annotations\n  -o, --output string   Path to the file to place the output in. Defaults to '-' which means stdout. (default &quot;-&quot;)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_genyaml_config.md",
    "content": "---\ntitle: telepresence genyaml config\ndescription: Generate YAML for the agent's entry in the telepresence-agents configmap.\nhide_table_of_contents: true\n---\n\nGenerate YAML for the agent's entry in the telepresence-agents configmap.\n\n## Synopsis:\n\nGenerate YAML for the agent's entry in the telepresence-agents configmap. See genyaml for more info on what this means\n\n### Usage:\n```\n  telepresence genyaml config [flags]\n```\n\n### Flags:\n```\n      --agent-image string         The qualified name of the agent image (default &quot;ghcr.io/telepresenceio/tel2:&lt;current version&gt;&quot;)\n      --agent-port uint16          The port number you wish the agent to listen on. (default 9900)\n  -h, --help                       help for config\n  -i, --input string               Path to the yaml containing the workload definition (i.e. Deployment, StatefulSet, etc). Pass '-' for stdin.. Mutually exclusive to --workload\n      --loglevel                   The loglevel for the generated traffic-agent sidecar (default INFO)\n      --manager-namespace string   The traffic-manager namespace (default &quot;ambassador&quot;)\n      --manager-port uint16        The traffic-manager API port (default 8081)\n  -n, --namespace string           If present, the namespace scope for this CLI request\n  -o, --output string              Path to the file to place the output in. Defaults to '-' which means stdout. (default &quot;-&quot;)\n  -w, --workload string            Name of the workload. If given, the workload will be retrieved from the cluster, mutually exclusive to --input\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_genyaml_container.md",
    "content": "---\ntitle: telepresence genyaml container\ndescription: Generate YAML for the traffic-agent container.\nhide_table_of_contents: true\n---\n\nGenerate YAML for the traffic-agent container.\n\n## Synopsis:\n\nGenerate YAML for the traffic-agent container. See genyaml for more info on what this means\n\n### Usage:\n```\n  telepresence genyaml container [flags]\n```\n\n### Flags:\n```\n  -a, --agent string       Path to the yaml containing the generated agent config\n  -h, --help               help for container\n  -i, --input string       Optional path to the yaml containing the workload definition (i.e. Deployment, StatefulSet, etc). Pass '-' for stdin. Loaded from cluster by default\n  -n, --namespace string   If present, the namespace scope for this CLI request\n  -o, --output string      Path to the file to place the output in. Defaults to '-' which means stdout. (default &quot;-&quot;)\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_genyaml_initcontainer.md",
    "content": "---\ntitle: telepresence genyaml initcontainer\ndescription: Generate YAML for the traffic-agent init container.\nhide_table_of_contents: true\n---\n\nGenerate YAML for the traffic-agent init container.\n\n## Synopsis:\n\nGenerate YAML for the traffic-agent init container. See genyaml for more info on what this means\n\n### Usage:\n```\n  telepresence genyaml initcontainer [flags]\n```\n\n### Flags:\n```\n  -a, --agent string       Path to the yaml containing the generated agent config\n  -h, --help               help for initcontainer\n  -n, --namespace string   If present, the namespace scope for this CLI request\n  -o, --output string      Path to the file to place the output in. Defaults to '-' which means stdout. (default &quot;-&quot;)\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_genyaml_volume.md",
    "content": "---\ntitle: telepresence genyaml volume\ndescription: Generate YAML for the traffic-agent volume.\nhide_table_of_contents: true\n---\n\nGenerate YAML for the traffic-agent volume.\n\n## Synopsis:\n\nGenerate YAML for the traffic-agent volume. See genyaml for more info on what this means\n\n### Usage:\n```\n  telepresence genyaml volume [flags]\n```\n\n### Flags:\n```\n  -a, --agent string       Path to the yaml containing the generated agent config\n  -h, --help               help for volume\n  -i, --input string       Optional path to the yaml containing the workload definition (i.e. Deployment, StatefulSet, etc). Pass '-' for stdin. Loaded from cluster by default\n  -n, --namespace string   If present, the namespace scope for this CLI request\n  -o, --output string      Path to the file to place the output in. Defaults to '-' which means stdout. (default &quot;-&quot;)\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_helm.md",
    "content": "---\ntitle: telepresence helm\ndescription: Helm commands using the embedded Telepresence Helm chart.\nhide_table_of_contents: true\n---\n\nHelm commands using the embedded Telepresence Helm chart.\n\n### Usage:\n```\n  telepresence helm [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [install](telepresence_helm_install) | Install telepresence traffic manager |\n| [lint](telepresence_helm_lint) | Verify the embedded telepresence Helm chart |\n| [uninstall](telepresence_helm_uninstall) | Uninstall telepresence traffic manager |\n| [upgrade](telepresence_helm_upgrade) | Upgrade telepresence traffic manager |\n| [version](telepresence_helm_version) | Print the version of the Helm client |\n\n### Flags:\n```\n  -h, --help   help for helm\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence helm [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_helm_install.md",
    "content": "---\ntitle: telepresence helm install\ndescription: Install telepresence traffic manager\nhide_table_of_contents: true\n---\n\nInstall telepresence traffic manager\n\n### Usage:\n```\n  telepresence helm install [flags]\n```\n\n### Flags:\n```\n      --allow-conflicting-subnets strings   Comma separated list of CIDR that will be allowed to conflict with local subnets\n      --also-proxy strings                  Additional comma separated list of CIDR to proxy\n      --create-namespace                    create a namespace for the traffic-manager if not present (default true)\n      --docker                              Start, or connect to, daemon in a docker container\n      --expose stringArray                  Port that a containerized daemon will expose. See docker run -p for more info. Can be repeated\n  -h, --help                                help for install\n      --hostname string                     Hostname used by a containerized daemon\n      --manager-namespace string            The namespace where the traffic manager is to be found. Overrides any other manager namespace set in config\n      --mapped-namespaces strings           Comma separated list of namespaces considered by DNS resolver and NAT for outbound connections. Defaults to all namespaces\n      --name string                         Optional name to use for the connection\n  -n, --namespace string                    If present, the namespace scope for this CLI request\n      --never-proxy strings                 Comma separated list of CIDR to never proxy\n      --no-hooks                            prevent hooks from running during install\n      --proxy-via strings                   Use Network Address Translation to create virtual IPs for the given CIDR, and route via WORKLOAD. Must be in the form CIDR=WORKLOAD. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n      --reroute-local strings               Reroute port on local host to remote host. Format is &lt;local port&gt;:&lt;host&gt;:&lt;port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --reroute-remote strings              Reroute port on remote host. Format is &lt;host&gt;:&lt;port&gt;:&lt;new port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --set stringArray                     specify a value as a.b=v (can specify multiple or separate values with commas: a.b=v1,a.c=v2)\n      --set-file stringArray                set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)\n      --set-json stringArray                set JSON values on the command line (can specify multiple or separate values with commas: a.b=jsonval1,a.c=jsonval2)\n      --set-string stringArray              set STRING values on the command line (can specify multiple or separate values with commas: a.b=val1,a.c=val2)\n  -f, --values stringArray                  specify values in a YAML file or a URL (can specify multiple)\n      --version string                      the telepresence version if different from the client's version. May be a range (e.g. ^2.21.0)\n      --vnat strings                        Use Network Address Translation to create virtual IPs for the given CIDR. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_helm_lint.md",
    "content": "---\ntitle: telepresence helm lint\ndescription: Verify the embedded telepresence Helm chart\nhide_table_of_contents: true\n---\n\nVerify the embedded telepresence Helm chart\n\n### Usage:\n```\n  telepresence helm lint [flags]\n```\n\n### Flags:\n```\n      --allow-conflicting-subnets strings   Comma separated list of CIDR that will be allowed to conflict with local subnets\n      --also-proxy strings                  Additional comma separated list of CIDR to proxy\n      --docker                              Start, or connect to, daemon in a docker container\n      --expose stringArray                  Port that a containerized daemon will expose. See docker run -p for more info. Can be repeated\n  -h, --help                                help for lint\n      --hostname string                     Hostname used by a containerized daemon\n      --manager-namespace string            The namespace where the traffic manager is to be found. Overrides any other manager namespace set in config\n      --mapped-namespaces strings           Comma separated list of namespaces considered by DNS resolver and NAT for outbound connections. Defaults to all namespaces\n      --name string                         Optional name to use for the connection\n  -n, --namespace string                    If present, the namespace scope for this CLI request\n      --never-proxy strings                 Comma separated list of CIDR to never proxy\n      --proxy-via strings                   Use Network Address Translation to create virtual IPs for the given CIDR, and route via WORKLOAD. Must be in the form CIDR=WORKLOAD. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n      --reroute-local strings               Reroute port on local host to remote host. Format is &lt;local port&gt;:&lt;host&gt;:&lt;port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --reroute-remote strings              Reroute port on remote host. Format is &lt;host&gt;:&lt;port&gt;:&lt;new port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --set stringArray                     specify a value as a.b=v (can specify multiple or separate values with commas: a.b=v1,a.c=v2)\n      --set-file stringArray                set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)\n      --set-json stringArray                set JSON values on the command line (can specify multiple or separate values with commas: a.b=jsonval1,a.c=jsonval2)\n      --set-string stringArray              set STRING values on the command line (can specify multiple or separate values with commas: a.b=val1,a.c=val2)\n  -f, --values stringArray                  specify values in a YAML file or a URL (can specify multiple)\n      --version string                      the telepresence version if different from the client's version. May be a range (e.g. ^2.21.0)\n      --vnat strings                        Use Network Address Translation to create virtual IPs for the given CIDR. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_helm_uninstall.md",
    "content": "---\ntitle: telepresence helm uninstall\ndescription: Uninstall telepresence traffic manager\nhide_table_of_contents: true\n---\n\nUninstall telepresence traffic manager\n\n### Usage:\n```\n  telepresence helm uninstall [flags]\n```\n\n### Flags:\n```\n      --allow-conflicting-subnets strings   Comma separated list of CIDR that will be allowed to conflict with local subnets\n      --also-proxy strings                  Additional comma separated list of CIDR to proxy\n      --docker                              Start, or connect to, daemon in a docker container\n      --expose stringArray                  Port that a containerized daemon will expose. See docker run -p for more info. Can be repeated\n  -h, --help                                help for uninstall\n      --hostname string                     Hostname used by a containerized daemon\n      --manager-namespace string            The namespace where the traffic manager is to be found. Overrides any other manager namespace set in config\n      --mapped-namespaces strings           Comma separated list of namespaces considered by DNS resolver and NAT for outbound connections. Defaults to all namespaces\n      --name string                         Optional name to use for the connection\n  -n, --namespace string                    If present, the namespace scope for this CLI request\n      --never-proxy strings                 Comma separated list of CIDR to never proxy\n      --no-hooks                            prevent hooks from running during uninstallation\n      --proxy-via strings                   Use Network Address Translation to create virtual IPs for the given CIDR, and route via WORKLOAD. Must be in the form CIDR=WORKLOAD. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n      --reroute-local strings               Reroute port on local host to remote host. Format is &lt;local port&gt;:&lt;host&gt;:&lt;port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --reroute-remote strings              Reroute port on remote host. Format is &lt;host&gt;:&lt;port&gt;:&lt;new port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --vnat strings                        Use Network Address Translation to create virtual IPs for the given CIDR. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_helm_upgrade.md",
    "content": "---\ntitle: telepresence helm upgrade\ndescription: Upgrade telepresence traffic manager\nhide_table_of_contents: true\n---\n\nUpgrade telepresence traffic manager\n\n### Usage:\n```\n  telepresence helm upgrade [flags]\n```\n\n### Flags:\n```\n      --allow-conflicting-subnets strings   Comma separated list of CIDR that will be allowed to conflict with local subnets\n      --also-proxy strings                  Additional comma separated list of CIDR to proxy\n      --create-namespace                    create the release namespace if not present (default true)\n      --docker                              Start, or connect to, daemon in a docker container\n      --expose stringArray                  Port that a containerized daemon will expose. See docker run -p for more info. Can be repeated\n  -h, --help                                help for upgrade\n      --hostname string                     Hostname used by a containerized daemon\n      --manager-namespace string            The namespace where the traffic manager is to be found. Overrides any other manager namespace set in config\n      --mapped-namespaces strings           Comma separated list of namespaces considered by DNS resolver and NAT for outbound connections. Defaults to all namespaces\n      --name string                         Optional name to use for the connection\n  -n, --namespace string                    If present, the namespace scope for this CLI request\n      --never-proxy strings                 Comma separated list of CIDR to never proxy\n      --no-hooks                            disable pre/post upgrade hooks\n      --proxy-via strings                   Use Network Address Translation to create virtual IPs for the given CIDR, and route via WORKLOAD. Must be in the form CIDR=WORKLOAD. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n      --reroute-local strings               Reroute port on local host to remote host. Format is &lt;local port&gt;:&lt;host&gt;:&lt;port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --reroute-remote strings              Reroute port on remote host. Format is &lt;host&gt;:&lt;port&gt;:&lt;new port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --reset-values                        when upgrading, reset the values to the ones built into the chart\n      --reuse-values                        when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f\n      --set stringArray                     specify a value as a.b=v (can specify multiple or separate values with commas: a.b=v1,a.c=v2)\n      --set-file stringArray                set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)\n      --set-json stringArray                set JSON values on the command line (can specify multiple or separate values with commas: a.b=jsonval1,a.c=jsonval2)\n      --set-string stringArray              set STRING values on the command line (can specify multiple or separate values with commas: a.b=val1,a.c=val2)\n  -f, --values stringArray                  specify values in a YAML file or a URL (can specify multiple)\n      --version string                      the telepresence version if different from the client's version. May be a range (e.g. ^2.21.0)\n      --vnat strings                        Use Network Address Translation to create virtual IPs for the given CIDR. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_helm_version.md",
    "content": "---\ntitle: telepresence helm version\ndescription: Print the version of the Helm client\nhide_table_of_contents: true\n---\n\nPrint the version of the Helm client\n\n### Usage:\n```\n  telepresence helm version [flags]\n```\n\n### Flags:\n```\n  -h, --help   help for version\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_ingest.md",
    "content": "---\ntitle: telepresence ingest\ndescription: Ingest a container\nhide_table_of_contents: true\n---\n\nIngest a container\n\n### Usage:\n```\n  telepresence ingest [flags] &lt;name&gt; [-- [[docker run flags] &lt;image name&gt;] OR [&lt;command&gt;]] args...]\n```\n\n### Flags:\n```\n  -c, --container string               Name of container that provides the environment and mounts for the ingest\n      --docker-build string            Build a Docker container from the given docker-context (path or URL), and run it with ingested environment and volume mounts, by passing arguments after -- to 'docker run', e.g. '--docker-build /path/to/docker/context -- -it IMAGE /bin/bash'\n      --docker-build-opt stringArray   Options to docker-build in the form key=value, e.g. --docker-build-opt tag=mytag.\n      --docker-debug string            Like --docker-build, but allows a debugger to run inside the container with relaxed security\n      --docker-mount string            The volume mount point in docker. Defaults to same as &quot;--mount&quot;\n      --docker-run                     Run a Docker container with ingested environment, volume mount, by passing arguments after -- to 'docker run', e.g. '--docker-run -- -it --rm ubuntu:20.04 /bin/bash'\n  -e, --env-file string                Also emit the remote environment to an file. The syntax used in the file can be determined using flag --env-syntax\n  -j, --env-json string                Also emit the remote environment to a file as a JSON blob.\n      --env-syntax string              Syntax used for env-file. One of &quot;docker&quot;, &quot;compose&quot;, &quot;sh&quot;, &quot;csh&quot;, &quot;cmd&quot;, &quot;json&quot;, and &quot;ps&quot;; where &quot;sh&quot;, &quot;csh&quot;, and &quot;ps&quot; can be suffixed with &quot;:export&quot; (default &quot;docker&quot;)\n  -h, --help                           help for ingest\n      --local-mount-port uint16        Do not mount remote directories. Instead, expose this port on localhost to an external mounter\n      --mount string                   The absolute path for the root directory where volumes will be mounted, $TELEPRESENCE_ROOT. Use &quot;true&quot; to have Telepresence pick a random mount point (default). Use &quot;false&quot; to disable filesystem mounting entirely. (default &quot;true&quot;)\n      --to-pod strings                 An additional port to forward from the ingested pod, will be made available at localhost:PORT Use this to, for example, access proxy/helper sidecars in the ingested pod. The default protocol is TCP. Use &lt;port&gt;/UDP for UDP ports\n      --wait-message string            Message to print when ingest handler has started\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_intercept.md",
    "content": "---\ntitle: telepresence intercept\ndescription: Intercept a service\nhide_table_of_contents: true\n---\n\nIntercept a service\n\n### Usage:\n```\n  telepresence intercept [flags] &lt;name&gt; [-- [[docker run flags] &lt;image name&gt;] OR [&lt;command&gt;]] args...]\n```\n\n### Flags:\n```\n      --address string                 Local address to forward to, e.g. '--address 10.0.0.2' (default &quot;127.0.0.1&quot; or name of container)\n      --container string               Name of container that provides the environment and mounts for the intercept. Defaults to the container matching the first intercepted port.\n      --detailed-output                Provide very detailed info about the intercept when used together with --output=json or --output=yaml'\n      --docker-build string            Build a Docker container from the given docker-context (path or URL), and run it with intercepted environment and volume mounts, by passing arguments after -- to 'docker run', e.g. '--docker-build /path/to/docker/context -- -it IMAGE /bin/bash'\n      --docker-build-opt stringArray   Options to docker-build in the form key=value, e.g. --docker-build-opt tag=mytag.\n      --docker-debug string            Like --docker-build, but allows a debugger to run inside the container with relaxed security\n      --docker-mount string            The volume mount point in docker. Defaults to same as &quot;--mount&quot;\n      --docker-run                     Run a Docker container with intercepted environment, volume mount, by passing arguments after -- to 'docker run', e.g. '--docker-run -- -it --rm ubuntu:20.04 /bin/bash'\n  -e, --env-file string                Also emit the remote environment to an file. The syntax used in the file can be determined using flag --env-syntax\n  -j, --env-json string                Also emit the remote environment to a file as a JSON blob.\n      --env-syntax string              Syntax used for env-file. One of &quot;docker&quot;, &quot;compose&quot;, &quot;sh&quot;, &quot;csh&quot;, &quot;cmd&quot;, &quot;json&quot;, and &quot;ps&quot;; where &quot;sh&quot;, &quot;csh&quot;, and &quot;ps&quot; can be suffixed with &quot;:export&quot; (default &quot;docker&quot;)\n  -h, --help                           help for intercept\n      --http-header strings            HTTP header filters. Only requests with matching headers will be intercepted. Supports both formats: --http-header &quot;X-User-ID=dev123&quot; or --http-header &quot;X-User-ID: dev123&quot; (curl -H compatible). Multiple headers use AND logic.\n      --http-path-equal strings        HTTP path filters. Only requests with matching paths will be intercepted. Exact path matching.\n      --http-path-prefix strings       HTTP path prefix filters. Only requests with matching path prefixes will be intercepted.\n      --http-path-regex strings        HTTP path regex filters. Only requests with paths matching the regex will be intercepted.\n      --local-mount-port uint16        Do not mount remote directories. Instead, expose this port on localhost to an external mounter\n      --mechanism mechanism            Which extension mechanism to use (default &quot;tcp&quot;)\n      --metadata strings               Metadata to attach to the intercept. Use --metadata key=value to set a single key/value pair, or --metadata key1=value1 --metadata key2=value2 to set multiple key/value pairs. The metadata can be retrieved using the Telepresence API server.\n      --mount string                   The absolute path for the root directory where volumes will be mounted, $TELEPRESENCE_ROOT. Use &quot;true&quot; to have Telepresence pick a random mount point (default). Use &quot;false&quot; to disable filesystem mounting entirely. Append &quot;:ro&quot; to mount everything read-only. (default &quot;true&quot;)\n      --plaintext                      Use plaintext instead of TLS when communicating with the intercept handler\n  -p, --port strings                   Local ports to forward to. Use &lt;local port&gt;:&lt;identifier&gt; to uniquely identify service ports, where the &lt;identifier&gt; is the port name or number. With --docker-run and a daemon that doesn't run in docker', use &lt;local port&gt;:&lt;container port&gt; or &lt;local port&gt;:&lt;container port&gt;:&lt;identifier&gt;.\n      --replace                        Indicates if the traffic-agent should replace application containers in workload pods. The default behavior is for the agent sidecar to be installed alongside existing containers. (DEPRECATED: Use the replace command.)\n      --service string                 Optional name of service to intercept. Sometimes needed to uniquely identify the intercepted port.\n      --to-pod strings                 Additional ports to forward to the intercepted pod, will available for connections to localhost:PORT. Use this to, for example, access proxy/helper sidecars in the intercepted pod. The default protocol is TCP. Use &lt;port&gt;/UDP for UDP ports\n      --wait-message string            Message to print when intercept handler has started\n  -w, --workload string                Name of workload (Deployment, ReplicaSet, StatefulSet, Rollout) to intercept, if different from &lt;name&gt;\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_leave.md",
    "content": "---\ntitle: telepresence leave\ndescription: Remove existing intercept\nhide_table_of_contents: true\n---\n\nRemove existing intercept\n\n### Usage:\n```\n  telepresence leave [flags] &lt;intercept_name&gt;\n```\n\n### Flags:\n```\n  -c, --container string   Container name\n  -h, --help               help for leave\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_list-contexts.md",
    "content": "---\ntitle: telepresence list-contexts\ndescription: Show all contexts\nhide_table_of_contents: true\n---\n\nShow all contexts\n\n### Usage:\n```\n  telepresence list-contexts [flags]\n```\n\n### Flags:\n```\n      --allow-conflicting-subnets strings   Comma separated list of CIDR that will be allowed to conflict with local subnets\n      --also-proxy strings                  Additional comma separated list of CIDR to proxy\n      --docker                              Start, or connect to, daemon in a docker container\n      --expose stringArray                  Port that a containerized daemon will expose. See docker run -p for more info. Can be repeated\n  -h, --help                                help for list-contexts\n      --hostname string                     Hostname used by a containerized daemon\n      --manager-namespace string            The namespace where the traffic manager is to be found. Overrides any other manager namespace set in config\n      --mapped-namespaces strings           Comma separated list of namespaces considered by DNS resolver and NAT for outbound connections. Defaults to all namespaces\n      --name string                         Optional name to use for the connection\n  -n, --namespace string                    If present, the namespace scope for this CLI request\n      --never-proxy strings                 Comma separated list of CIDR to never proxy\n      --proxy-via strings                   Use Network Address Translation to create virtual IPs for the given CIDR, and route via WORKLOAD. Must be in the form CIDR=WORKLOAD. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n      --reroute-local strings               Reroute port on local host to remote host. Format is &lt;local port&gt;:&lt;host&gt;:&lt;port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --reroute-remote strings              Reroute port on remote host. Format is &lt;host&gt;:&lt;port&gt;:&lt;new port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --vnat strings                        Use Network Address Translation to create virtual IPs for the given CIDR. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_list-namespaces.md",
    "content": "---\ntitle: telepresence list-namespaces\ndescription: Show all namespaces\nhide_table_of_contents: true\n---\n\nShow all namespaces\n\n### Usage:\n```\n  telepresence list-namespaces [flags]\n```\n\n### Flags:\n```\n      --allow-conflicting-subnets strings   Comma separated list of CIDR that will be allowed to conflict with local subnets\n      --also-proxy strings                  Additional comma separated list of CIDR to proxy\n      --docker                              Start, or connect to, daemon in a docker container\n      --expose stringArray                  Port that a containerized daemon will expose. See docker run -p for more info. Can be repeated\n  -h, --help                                help for list-namespaces\n      --hostname string                     Hostname used by a containerized daemon\n      --manager-namespace string            The namespace where the traffic manager is to be found. Overrides any other manager namespace set in config\n      --mapped-namespaces strings           Comma separated list of namespaces considered by DNS resolver and NAT for outbound connections. Defaults to all namespaces\n      --name string                         Optional name to use for the connection\n  -n, --namespace string                    If present, the namespace scope for this CLI request\n      --never-proxy strings                 Comma separated list of CIDR to never proxy\n      --proxy-via strings                   Use Network Address Translation to create virtual IPs for the given CIDR, and route via WORKLOAD. Must be in the form CIDR=WORKLOAD. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n      --reroute-local strings               Reroute port on local host to remote host. Format is &lt;local port&gt;:&lt;host&gt;:&lt;port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --reroute-remote strings              Reroute port on remote host. Format is &lt;host&gt;:&lt;port&gt;:&lt;new port&gt;[/{tcp,udp}]. &lt;port&gt; can be symbolic when &lt;host&gt; is a service name.\n      --vnat strings                        Use Network Address Translation to create virtual IPs for the given CIDR. CIDR can be substituted for the symblic name &quot;service&quot;, &quot;pods&quot;, &quot;also&quot;, or &quot;all&quot;.\n```\n\n### Kubernetes flags:\n```\n      --as string                      Username to impersonate for the operation. User could be a regular user or a service account in a namespace.\n      --as-group stringArray           Group to impersonate for the operation, this flag can be repeated to specify multiple groups.\n      --as-uid string                  UID to impersonate for the operation.\n      --as-user-extra stringArray      User extras to impersonate for the operation, this flag can be repeated to specify multiple values for the same key.\n      --cache-dir string               Default cache directory (default &quot;$HOME/.kube/cache&quot;)\n      --certificate-authority string   Path to a cert file for the certificate authority\n      --client-certificate string      Path to a client certificate file for TLS\n      --client-key string              Path to a client key file for TLS\n      --cluster string                 The name of the kubeconfig cluster to use\n      --context string                 The name of the kubeconfig context to use\n      --disable-compression            If true, opt-out of response compression for all requests to the server\n      --insecure-skip-tls-verify       If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure\n      --kubeconfig string              Path to the kubeconfig file to use for CLI requests.\n      --request-timeout string         The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default &quot;0&quot;)\n  -s, --server string                  The address and port of the Kubernetes API server\n      --tls-server-name string         Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used\n      --token string                   Bearer token for authentication to the API server\n      --user string                    The name of the kubeconfig user to use\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_list.md",
    "content": "---\ntitle: telepresence list\ndescription: List current intercepts\nhide_table_of_contents: true\n---\n\nList current intercepts\n\n### Usage:\n```\n  telepresence list [flags]\n```\n\n### Flags:\n```\n  -a, --agents             with installed agents only\n      --debug              include debugging information\n  -h, --help               help for list\n  -g, --ingests            ingests\n  -i, --intercepts         intercepts\n  -n, --namespace string   If present, the namespace scope for this CLI request\n  -r, --replacements       replacements\n  -t, --wiretaps           wiretaps\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_loglevel.md",
    "content": "---\ntitle: telepresence loglevel\ndescription: Temporarily change the log-level of the traffic-manager, traffic-agent, and user and root daemons\nhide_table_of_contents: true\n---\n\nTemporarily change the log-level of the traffic-manager, traffic-agent, and user and root daemons\n\n### Usage:\n```\n  telepresence loglevel &lt;ERROR,WARN,INFO,DEBUG,TRACE&gt; [flags]\n```\n\n### Flags:\n```\n  -d, --duration duration   The time that the log-level will be in effect (0s means indefinitely) (default 30m0s)\n  -h, --help                help for loglevel\n  -l, --local-only          Only affect the user and root daemons\n  -r, --remote-only         Only affect the traffic-manager and traffic-agents\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp.md",
    "content": "---\ntitle: telepresence mcp\ndescription: MCP server management\nhide_table_of_contents: true\n---\n\nMCP server management\n\n## Synopsis:\n\nManage MCP servers for AI assistants and code editors\n\n### Usage:\n```\n  telepresence mcp [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [claude](telepresence_mcp_claude) | Manage Claude Desktop MCP servers |\n| [cursor](telepresence_mcp_cursor) | Manage Cursor MCP servers |\n| [start](telepresence_mcp_start) | Start the MCP server |\n| [stream](telepresence_mcp_stream) | Stream the MCP server over HTTP |\n| [tools](telepresence_mcp_tools) | Export tools as JSON |\n| [vscode](telepresence_mcp_vscode) | Manage VSCode MCP servers |\n\n### Flags:\n```\n  -h, --help   help for mcp\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence mcp [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_claude.md",
    "content": "---\ntitle: telepresence mcp claude\ndescription: Manage Claude Desktop MCP servers\nhide_table_of_contents: true\n---\n\nManage Claude Desktop MCP servers\n\n## Synopsis:\n\nManage MCP server configuration for Claude Desktop\n\n### Usage:\n```\n  telepresence mcp claude [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [disable](telepresence_mcp_claude_disable) | Remove server from Claude config |\n| [enable](telepresence_mcp_claude_enable) | Add server to Claude config |\n| [list](telepresence_mcp_claude_list) | Show Claude MCP servers |\n\n### Flags:\n```\n  -h, --help   help for claude\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence mcp claude [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_claude_disable.md",
    "content": "---\ntitle: telepresence mcp claude disable\ndescription: Remove server from Claude config\nhide_table_of_contents: true\n---\n\nRemove server from Claude config\n\n## Synopsis:\n\nRemove this application from Claude Desktop MCP servers\n\n### Usage:\n```\n  telepresence mcp claude disable [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to Claude config file\n  -h, --help                 help for disable\n      --server-name string   Name of the MCP server to remove (default: derived from executable name)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_claude_enable.md",
    "content": "---\ntitle: telepresence mcp claude enable\ndescription: Add server to Claude config\nhide_table_of_contents: true\n---\n\nAdd server to Claude config\n\n## Synopsis:\n\nAdd this application as an MCP server in Claude Desktop\n\n### Usage:\n```\n  telepresence mcp claude enable [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to Claude config file\n  -e, --env stringToString   Environment variables (e.g., --env KEY1=value1 --env KEY2=value2) (default [])\n  -h, --help                 help for enable\n      --log-level string     Log level (debug, info, warn, error)\n      --server-name string   Name for the MCP server (default: derived from executable name)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_claude_list.md",
    "content": "---\ntitle: telepresence mcp claude list\ndescription: Show Claude MCP servers\nhide_table_of_contents: true\n---\n\nShow Claude MCP servers\n\n## Synopsis:\n\nShow all MCP servers configured in Claude Desktop\n\n### Usage:\n```\n  telepresence mcp claude list [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to Claude config file\n  -h, --help                 help for list\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_cursor.md",
    "content": "---\ntitle: telepresence mcp cursor\ndescription: Manage Cursor MCP servers\nhide_table_of_contents: true\n---\n\nManage Cursor MCP servers\n\n## Synopsis:\n\nManage MCP server configuration for Cursor\n\n### Usage:\n```\n  telepresence mcp cursor [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [disable](telepresence_mcp_cursor_disable) | Remove server from Cursor config |\n| [enable](telepresence_mcp_cursor_enable) | Add server to Cursor config |\n| [list](telepresence_mcp_cursor_list) | Show Cursor MCP servers |\n\n### Flags:\n```\n  -h, --help   help for cursor\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence mcp cursor [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_cursor_disable.md",
    "content": "---\ntitle: telepresence mcp cursor disable\ndescription: Remove server from Cursor config\nhide_table_of_contents: true\n---\n\nRemove server from Cursor config\n\n## Synopsis:\n\nRemove this application from Cursor MCP servers\n\n### Usage:\n```\n  telepresence mcp cursor disable [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to Cursor config file\n  -h, --help                 help for disable\n      --server-name string   Name of the MCP server to remove (default: derived from executable name)\n      --workspace            Remove from workspace settings (.cursor/mcp.json) instead of user settings\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_cursor_enable.md",
    "content": "---\ntitle: telepresence mcp cursor enable\ndescription: Add server to Cursor config\nhide_table_of_contents: true\n---\n\nAdd server to Cursor config\n\n## Synopsis:\n\nAdd this application as an MCP server in Cursor\n\n### Usage:\n```\n  telepresence mcp cursor enable [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to Cursor config file\n  -e, --env stringToString   Environment variables (e.g., --env KEY1=value1 --env KEY2=value2) (default [])\n  -h, --help                 help for enable\n      --log-level string     Log level (debug, info, warn, error)\n      --server-name string   Name for the MCP server (default: derived from executable name)\n      --workspace            Add to workspace settings (.cursor/mcp.json) instead of user settings\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_cursor_list.md",
    "content": "---\ntitle: telepresence mcp cursor list\ndescription: Show Cursor MCP servers\nhide_table_of_contents: true\n---\n\nShow Cursor MCP servers\n\n## Synopsis:\n\nShow all MCP servers configured in Cursor\n\n### Usage:\n```\n  telepresence mcp cursor list [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to Cursor config file\n  -h, --help                 help for list\n      --workspace            List from workspace settings (.cursor/mcp.json) instead of user settings\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_start.md",
    "content": "---\ntitle: telepresence mcp start\ndescription: Start the MCP server\nhide_table_of_contents: true\n---\n\nStart the MCP server\n\n## Synopsis:\n\nStart stdio server to expose CLI commands to AI assistants\n\n### Usage:\n```\n  telepresence mcp start [flags]\n```\n\n### Flags:\n```\n  -h, --help               help for start\n      --log-level string   Log level (debug, info, warn, error)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_stream.md",
    "content": "---\ntitle: telepresence mcp stream\ndescription: Stream the MCP server over HTTP\nhide_table_of_contents: true\n---\n\nStream the MCP server over HTTP\n\n## Synopsis:\n\nStart HTTP server to expose CLI commands to AI assistants\n\n### Usage:\n```\n  telepresence mcp stream [flags]\n```\n\n### Flags:\n```\n  -h, --help               help for stream\n      --host string        host to listen on\n      --log-level string   Log level (debug, info, warn, error)\n      --port int           port number to listen on (default 8080)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_tools.md",
    "content": "---\ntitle: telepresence mcp tools\ndescription: Export tools as JSON\nhide_table_of_contents: true\n---\n\nExport tools as JSON\n\n## Synopsis:\n\nExport available MCP tools to mcp-tools.json for inspection\n\n### Usage:\n```\n  telepresence mcp tools [flags]\n```\n\n### Flags:\n```\n  -h, --help               help for tools\n      --log-level string   Log level (debug, info, warn, error)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_vscode.md",
    "content": "---\ntitle: telepresence mcp vscode\ndescription: Manage VSCode MCP servers\nhide_table_of_contents: true\n---\n\nManage VSCode MCP servers\n\n## Synopsis:\n\nManage MCP server configuration for Visual Studio Code\n\n### Usage:\n```\n  telepresence mcp vscode [command] [flags]\n```\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n| [disable](telepresence_mcp_vscode_disable) | Remove server from VSCode config |\n| [enable](telepresence_mcp_vscode_enable) | Add server to VSCode config |\n| [list](telepresence_mcp_vscode_list) | Show VSCode MCP servers |\n\n### Flags:\n```\n  -h, --help   help for vscode\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n\nUse `telepresence mcp vscode [command] --help` for more information about a command.\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_vscode_disable.md",
    "content": "---\ntitle: telepresence mcp vscode disable\ndescription: Remove server from VSCode config\nhide_table_of_contents: true\n---\n\nRemove server from VSCode config\n\n## Synopsis:\n\nRemove this application from VSCode MCP servers\n\n### Usage:\n```\n  telepresence mcp vscode disable [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to VSCode config file\n  -h, --help                 help for disable\n      --server-name string   Name of the MCP server to remove (default: derived from executable name)\n      --workspace            Remove from workspace settings (.vscode/mcp.json) instead of user settings\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_vscode_enable.md",
    "content": "---\ntitle: telepresence mcp vscode enable\ndescription: Add server to VSCode config\nhide_table_of_contents: true\n---\n\nAdd server to VSCode config\n\n## Synopsis:\n\nAdd this application as an MCP server in VSCode\n\n### Usage:\n```\n  telepresence mcp vscode enable [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to VSCode config file\n  -e, --env stringToString   Environment variables (e.g., --env KEY1=value1 --env KEY2=value2) (default [])\n  -h, --help                 help for enable\n      --log-level string     Log level (debug, info, warn, error)\n      --server-name string   Name for the MCP server (default: derived from executable name)\n      --workspace            Add to workspace settings (.vscode/mcp.json) instead of user settings\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_mcp_vscode_list.md",
    "content": "---\ntitle: telepresence mcp vscode list\ndescription: Show VSCode MCP servers\nhide_table_of_contents: true\n---\n\nShow VSCode MCP servers\n\n## Synopsis:\n\nShow all MCP servers configured in VSCode\n\n### Usage:\n```\n  telepresence mcp vscode list [flags]\n```\n\n### Flags:\n```\n      --config-path string   Path to VSCode config file\n  -h, --help                 help for list\n      --workspace            List from workspace settings (.vscode/mcp.json) instead of user settings\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_quit.md",
    "content": "---\ntitle: telepresence quit\ndescription: Tell telepresence daemons to quit\nhide_table_of_contents: true\n---\n\nTell telepresence daemons to quit\n\n### Usage:\n```\n  telepresence quit [flags]\n```\n\n### Flags:\n```\n  -h, --help           help for quit\n  -s, --stop-daemons   stop all local telepresence daemons\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_replace.md",
    "content": "---\ntitle: telepresence replace\ndescription: Replace a container\nhide_table_of_contents: true\n---\n\nReplace a container\n\n### Usage:\n```\n  telepresence replace [flags] &lt;name&gt; [-- [[docker run flags] &lt;image name&gt;] OR [&lt;command&gt;]] args...]\n```\n\n### Flags:\n```\n      --address string                 Local address to forward to, e.g. '--address 10.0.0.2' (default &quot;127.0.0.1&quot; or name of container)\n      --container string               Name of container that should be replaced. Can be omitted if the workload only has one container.\n      --detailed-output                Provide very detailed info about the replace when used together with --output=json or --output=yaml'\n      --docker-build string            Build a Docker container from the given docker-context (path or URL), and run it with replaced environment and volume mounts, by passing arguments after -- to 'docker run', e.g. '--docker-build /path/to/docker/context -- -it IMAGE /bin/bash'\n      --docker-build-opt stringArray   Options to docker-build in the form key=value, e.g. --docker-build-opt tag=mytag.\n      --docker-debug string            Like --docker-build, but allows a debugger to run inside the container with relaxed security\n      --docker-mount string            The volume mount point in docker. Defaults to same as &quot;--mount&quot;\n      --docker-run                     Run a Docker container with replaced environment, volume mount, by passing arguments after -- to 'docker run', e.g. '--docker-run -- -it --rm ubuntu:20.04 /bin/bash'\n  -e, --env-file string                Also emit the remote environment to an file. The syntax used in the file can be determined using flag --env-syntax\n  -j, --env-json string                Also emit the remote environment to a file as a JSON blob.\n      --env-syntax string              Syntax used for env-file. One of &quot;docker&quot;, &quot;compose&quot;, &quot;sh&quot;, &quot;csh&quot;, &quot;cmd&quot;, &quot;json&quot;, and &quot;ps&quot;; where &quot;sh&quot;, &quot;csh&quot;, and &quot;ps&quot; can be suffixed with &quot;:export&quot; (default &quot;docker&quot;)\n  -h, --help                           help for replace\n      --local-mount-port uint16        Do not mount remote directories. Instead, expose this port on localhost to an external mounter\n      --mount string                   The absolute path for the root directory where volumes will be mounted, $TELEPRESENCE_ROOT. Use &quot;true&quot; to have Telepresence pick a random mount point (default). Use &quot;false&quot; to disable filesystem mounting entirely. Append &quot;:ro&quot; to mount everything read-only. (default &quot;true&quot;)\n  -p, --port strings                   Local ports to forward to. Use &lt;local port&gt;:&lt;identifier&gt; to uniquely identify container ports, where the &lt;identifier&gt; is the port name or number. Use &quot;all&quot; (the default) to forward all ports declared in the replaced container to their corresponding local port.  (default [all])\n      --to-pod strings                 Additional ports to forward to the pod containing the replaced container, will available for connections to localhost:PORT. Use this to, for example, access proxy/helper sidecars in the pod. The default protocol is TCP. Use &lt;port&gt;/UDP for UDP ports\n      --wait-message string            Message to print when replace handler has started\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_revoke.md",
    "content": "---\ntitle: telepresence revoke\ndescription: Revoke an intercept by intercept ID. The intercept ID must be in the format &lt;session_id&gt;:&lt;intercept_name&gt;\nhide_table_of_contents: true\n---\n\nRevoke an intercept by intercept ID. The intercept ID must be in the format &lt;session_id&gt;:&lt;intercept_name&gt;\n\n## Synopsis:\n\nRevoke an intercept by intercept ID. This is an administrative operation that\nrequires RBAC permissions to modify the &quot;traffic-manager&quot; configmap.\n\n### Usage:\n```\n  telepresence revoke &lt;intercept_id&gt; [flags]\n```\n\n### Flags:\n```\n  -h, --help   help for revoke\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_serve.md",
    "content": "---\ntitle: telepresence serve\ndescription: Start the browser on a remote service\nhide_table_of_contents: true\n---\n\nStart the browser on a remote service\n\n### Usage:\n```\n  telepresence serve &lt;name of remote service&gt; [flags]\n```\n\n### Flags:\n```\n  -h, --help          help for serve\n  -p, --port uint16   service port (default 80)\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_status.md",
    "content": "---\ntitle: telepresence status\ndescription: Show connectivity status\nhide_table_of_contents: true\n---\n\nShow connectivity status\n\n### Usage:\n```\n  telepresence status [flags]\n```\n\n### Flags:\n```\n  -h, --help           help for status\n      --multi-daemon   always use multi-daemon output format, even if there's only one daemon connected\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_uninstall.md",
    "content": "---\ntitle: telepresence uninstall\ndescription: Uninstall telepresence agents\nhide_table_of_contents: true\n---\n\nUninstall telepresence agents\n\n### Usage:\n```\n  telepresence uninstall [flags] &lt;workloads...&gt;\n```\n\n### Flags:\n```\n  -a, --all-agents   uninstall intercept agent on all workloads\n  -h, --help         help for uninstall\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_version.md",
    "content": "---\ntitle: telepresence version\ndescription: Show version\nhide_table_of_contents: true\n---\n\nShow version\n\n### Usage:\n```\n  telepresence version [flags]\n```\n\n### Flags:\n```\n  -h, --help   help for version\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cli/telepresence_wiretap.md",
    "content": "---\ntitle: telepresence wiretap\ndescription: Wiretap a Service\nhide_table_of_contents: true\n---\n\nWiretap a Service\n\n### Usage:\n```\n  telepresence wiretap [flags] &lt;wiretap_base_name&gt; [-- &lt;command with arguments...&gt;]\n```\n\n### Flags:\n```\n      --address string                 Local address to forward to, e.g. '--address 10.0.0.2' (default &quot;127.0.0.1&quot; or name of container)\n      --container string               Name of container that provides the environment and mounts for the wiretap. Defaults to the container matching the first wiretapped port.\n      --detailed-output                Provide very detailed info about the wiretap when used together with --output=json or --output=yaml'\n      --docker-build string            Build a Docker container from the given docker-context (path or URL), and run it with wiretapped environment and volume mounts, by passing arguments after -- to 'docker run', e.g. '--docker-build /path/to/docker/context -- -it IMAGE /bin/bash'\n      --docker-build-opt stringArray   Options to docker-build in the form key=value, e.g. --docker-build-opt tag=mytag.\n      --docker-debug string            Like --docker-build, but allows a debugger to run inside the container with relaxed security\n      --docker-mount string            The volume mount point in docker. Defaults to same as &quot;--mount&quot;\n      --docker-run                     Run a Docker container with wiretapped environment, volume mount, by passing arguments after -- to 'docker run', e.g. '--docker-run -- -it --rm ubuntu:20.04 /bin/bash'\n  -e, --env-file string                Also emit the remote environment to an file. The syntax used in the file can be determined using flag --env-syntax\n  -j, --env-json string                Also emit the remote environment to a file as a JSON blob.\n      --env-syntax string              Syntax used for env-file. One of &quot;docker&quot;, &quot;compose&quot;, &quot;sh&quot;, &quot;csh&quot;, &quot;cmd&quot;, &quot;json&quot;, and &quot;ps&quot;; where &quot;sh&quot;, &quot;csh&quot;, and &quot;ps&quot; can be suffixed with &quot;:export&quot; (default &quot;docker&quot;)\n  -h, --help                           help for wiretap\n      --http-header strings            HTTP header filters. Only requests with matching headers will be wiretapped. Supports both formats: --http-header &quot;X-User-ID=dev123&quot; or --http-header &quot;X-User-ID: dev123&quot; (curl -H compatible). Multiple headers use AND logic.\n      --http-path-equal strings        HTTP path filters. Only requests with matching paths will be wiretapped. Exact path matching.\n      --http-path-prefix strings       HTTP path prefix filters. Only requests with matching path prefixes will be wiretapped.\n      --http-path-regex strings        HTTP path regex filters. Only requests with paths matching the regex will be wiretapped.\n      --local-mount-port uint16        Do not mount remote directories. Instead, expose this port on localhost to an external mounter\n      --mechanism mechanism            Which extension mechanism to use (default &quot;tcp&quot;)\n      --mount string                   The absolute path for the root directory where volumes will be mounted, $TELEPRESENCE_ROOT. Use &quot;true&quot; to have Telepresence pick a random mount point (default). Use &quot;false&quot; to disable filesystem mounting entirely. Append &quot;:ro&quot; to mount everything read-only. (default &quot;true&quot;)\n      --plaintext                      Use plaintext instead of TLS when communicating with the intercept handler\n  -p, --port strings                   Local ports to forward to. Use &lt;local port&gt;:&lt;identifier&gt; to uniquely identify service ports, where the &lt;identifier&gt; is the port name or number. With --docker-run and a daemon that doesn't run in docker', use &lt;local port&gt;:&lt;container port&gt; or &lt;local port&gt;:&lt;container port&gt;:&lt;identifier&gt;.\n      --service string                 Optional name of service to wiretap. Sometimes needed to uniquely identify the intercepted port.\n      --wait-message string            Message to print when wiretap handler has started\n  -w, --workload string                Name of workload (Deployment, ReplicaSet, StatefulSet, Rollout) to wiretap, if different from &lt;name&gt;\n```\n\n### Global Flags:\n```\n      --config string     Path to the Telepresence configuration file (default &quot;$HOME/.config/telepresence/config.yml&quot;)\n      --output string     Set the output format, supported values are 'json', 'yaml', and 'default' (default &quot;default&quot;)\n      --progress string   Set type of progress output (auto, tty, plain, json, quiet) (default &quot;auto&quot;)\n      --use string        Match expression that uniquely identifies the daemon container\n```\n"
  },
  {
    "path": "docs/reference/cluster-config.md",
    "content": "---\ntitle: Cluster-side configuration\n---\n# Cluster-side configuration\n\nFor the most part, Telepresence doesn't require any special\nconfiguration in the cluster and can be used right away in any\ncluster (as long as the user has adequate [RBAC permissions](rbac.md).\n\n## Helm Chart configuration\nSome cluster specific configuration can be provided when installing\nor upgrading the Telepresence cluster installation using Helm. Once\ninstalled, the Telepresence client will configure itself from values\nthat it receives when connecting to the Traffic manager.\n\nSee the Helm chart [README](https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss/$version$)\nfor a full list of available configuration settings.\n\n### Values\nTo add configuration, create a yaml file with the configuration values and then use it executing `telepresence helm install [--upgrade] --values <values yaml>`\n\n## Client Configuration\n\nIt is possible for the Traffic Manager to automatically push config to all\nconnecting clients. To learn more about this, please see the [client config docs](config.md#global-configuration).\n\n## gRCP connections\n\nAll traffic to and from the cluster is tunneled via gRPC over a kubernetes port-forward connection. Both the traffic-manager\nand the traffic-agent have gRPC servers that the clients connect to. They are configured using the `grpc` structure.\n\n| Field             | Description                                                                                  | Type                                              | Default  |\n|-------------------|----------------------------------------------------------------------------------------------|---------------------------------------------------|----------|\n| `connectionTTL`   | Max time that the traffic-manager or traffic-agent will keep an idle client connection alive | [duration](https://pkg.go.dev/time#ParseDuration) | `24h`    |\n| `maxReceiveSize`  | Max size of a gRCP message received by the traffic-manager or traffic-agent                  | [quantity](../common/quantity.md)                 | `4Mi`    |\n\n## Traffic Manager Configuration\n\nThe `trafficManager` structure of the Helm chart configures the behavior of the Telepresence traffic manager.\n\n## Agent Configuration\n\nThe `agent` structure of the Helm chart configures the behavior of the Telepresence agents.\n\n### Image Configuration\n\nThe `agent.image` structure contains the following values:\n\n| Setting    | Meaning                                                                     |\n|------------|-----------------------------------------------------------------------------|\n| `registry` | Registry used when downloading the image. Defaults to \"docker.io/datawire\". |\n| `name`     | The name of the image. Defaults to \"tel2\"                                   |\n| `tag`      | The tag of the image. Defaults to $version$                                 |\n\n### Log level\n\nThe `agent.LogLevel` controls the log level of the traffic-agent. See [Log Levels](config.md#log-levels) for more info.\n\n### Resources\n\nThe `agent.resources` and `agent.initResources` will be used as the `resources` element when injecting traffic-agents and init-containers.\n\n## Mutating Webhook\n\nTelepresence uses a Mutating Webhook to inject the [Traffic Agent](architecture.md#traffic-agent) sidecar container and update the\nport definitions. This means that an engaged workload (Deployment, StatefulSet, ReplicaSet, ArgoRollout) will remain untouched\nand in sync as far as GitOps workflows (such as ArgoCD) are concerned.\n\nThe injection will happen on demand the first time an attempt is made to replace, ingest, intercept, or wiretap the workload.\n\nIf you want to prevent that the injection ever happens, simply add the `telepresence.io/inject-traffic-agent: disabled`\nannotation to your workload template's annotations:\n\n```diff\n spec:\n   template:\n     metadata:\n       labels:\n         service: your-service\n+      annotations:\n+        telepresence.io/inject-traffic-agent: disabled\n     spec:\n       containers:\n```\n\n### Service Name and Port Annotations\n\nTelepresence will automatically find all services and all ports that will connect to a workload and make them available\nfor an intercept, but you can explicitly define that only one service and/or port can be intercepted.\n\n```diff\n spec:\n   template:\n     metadata:\n       labels:\n         service: your-service\n       annotations:\n+        telepresence.io/inject-service-name: my-service\n+        telepresence.io/inject-service-ports: https\n     spec:\n       containers:\n```\n\n### Control Volume Sharing\n\nTelepresence enables control over what volumes that will be shared with connecting clients using mount policies. A\npolicy can be declared for a volume name, or for paths matching a path prefix, and can be added either as a Helm\nchart value using `agents.mountPolicies` or using the workload annotation `telepresence.io/mount-policies`.\n\nPossible Mount Policies are:\n\n| Policy         | Meaning                                                                                              |\n|----------------|------------------------------------------------------------------------------------------------------|\n| Ignore         | Do not share this volume with engaging clients                                                       |\n| Local          | Do not share this volume with engaging clients, instead Mount it using the client's local filesystem |\n| Remote         | Share this volume, and give engaging clients read and write access to it                             |\n| RemoteReadOnly | Like Remote, but with read-only access                                                               |\n\nExample Helm chart value:\n```yaml\nagents:\n  mountPolicies:\n    '/tmp': Local\n    certs: RemoteReadOnly\n    private: Ignore\n```\n\nExample using the `telepresence.io/mount-policies` annotation:\n```yaml\nspec:\n  template:\n    metadata:\n      annotations:\n        'telepresence.io/mount-policies': '{\"/tmp\":\"Local\",\"certs\":\"RemoteReadOnly\",\"private\":\"Ignore\"}'\n```\n\nThe annotation `telepresence.io/inject-ignore-volume-mounts` can be used if the objective is to just ignore\nvolume mounts, but it's recommended to always use the `telepresence.io/mount-policies` annotation.\n\nExample using the `telepresence.io/inject-ignore-volume-mounts` annotation:\n\n```yaml\n spec:\n   template:\n     metadata:\n       annotations:\n         telepresence.io/inject-ignore-volume-mounts: \"private\"\n```\n\nThe example is equivalent to:\n```yaml\n spec:\n   template:\n     metadata:\n       annotations:\n         telepresence.io/mount-policies: '{\"private\":\"Ignore\"}'\n```\n\n### Note on Numeric Ports\n\nIf the `targetPort` of your intercepted service is pointing at a port number, in addition to\ninjecting the Traffic Agent sidecar, Telepresence will also inject an `initContainer` that will\nreconfigure the pod's firewall rules to redirect traffic to the Traffic Agent.\n\n> [!IMPORTANT]\n> Note that this `initContainer` requires `NET_ADMIN` capabilities. If your cluster administrator has disabled them, you will be unable to use numeric ports with the agent injector.\n\nFor example, the following service is using a numeric port, so Telepresence would inject an initContainer into it:\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: your-service\nspec:\n  type: ClusterIP\n  selector:\n    service: your-service\n  ports:\n    - port: 80\n      targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: your-service\n  labels:\n    service: your-service\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      service: your-service\n  template:\n    metadata:\n      annotations:\n        telepresence.io/inject-traffic-agent: enabled\n      labels:\n        service: your-service\n    spec:\n      containers:\n        - name: your-container\n          image: jmalloc/echo-server\n          ports:\n            - containerPort: 8080\n```\n\n## Intercept Configuration\n\n### Restricting Global Intercepts\n\nIn shared development environments, you may want to prevent developers from creating global TCP/UDP intercepts that would\nblock others from intercepting the same service. Telepresence allows you to restrict intercepts to only HTTP-based\nintercepts with header or path filters.\n\nWhen installing your traffic-manager through helm, use the `--set` flag:\n\n`telepresence helm install --set intercept.allowGlobalIntercepts=false`\n\nThis also applies when upgrading:\n\n`telepresence helm upgrade --set intercept.allowGlobalIntercepts=false`\n\nAlternatively, add it to your custom `values.yaml`:\n\n```yaml\nintercept:\n  allowGlobalIntercepts: false\n```\n\nWhen this setting is `false`:\n- Standard intercepts without HTTP filters (e.g., `telepresence intercept myservice --port 8080`) will be rejected\n- Replace operations (e.g., `telepresence replace myservice`) will be rejected\n- HTTP intercepts with filters (e.g., `telepresence intercept myservice --http-header X-User-ID=dev123 --port 8080`) will work normally\n- Wiretap operations continue to work (they don't block other users)\n- Multiple developers can create personal HTTP intercepts on the same service simultaneously\n- The error message will guide users to use `--http-header`, `--http-path-prefix`, `--http-path-equal`, or `--http-path-regex` flags\n\nThe setting defaults to `true` to maintain backward compatibility with existing deployments.\n\n### Excluding Envrionment Variables\n\nIf your pod contains sensitive variables like a database password, or third party API Key, you may want to exclude those from being propagated through an intercept.\nTelepresence allows you to configure this through a ConfigMap that is then read and removes the sensitive variables.\n\nThis can be done in two ways:\n\nWhen installing your traffic-manager through helm you can use the `--set` flag and pass a comma separated list of variables:\n\n`telepresence helm install --set intercept.environment.excluded=\"{DATABASE_PASSWORD,API_KEY}\"`\n\nThis also applies when upgrading:\n\n`telepresence helm upgrade --set intercept.environment.excluded=\"{DATABASE_PASSWORD,API_KEY}\"`\n\nOnce this is completed, the environment variables will no longer be in the environment file created by an Intercept.\n\nThe other way to complete this is in your custom `values.yaml`. Customizing your traffic-manager through a values file can be viewed [here](../install/manager.md).\n\n```yaml\nintercept:\n  environment:\n    excluded: ['DATABASE_PASSWORD', 'API_KEY']\n```\n\nYou can exclude any number of variables, they just need to match the `key` of the variable within a pod to be excluded.\n"
  },
  {
    "path": "docs/reference/compose.md",
    "content": "---\ntitle: Telepresence Docker Compose Extension\nhide_table_of_contents: true\n---\n# Telepresence Docker Compose Extension\n\nThe `x-tele` extension is added either at the top level or to a service. E.g.\n\n```yaml\nx-tele:\n  connections:\n    - namespace: <name>\nservices:\n  some-name:\n    x-tele:\n      type: <extension type>\n      ...\n```\n\nThose extensions are recognized by the `telepresence compose`, which acts as an extended `docker compose` command. Telepresence will create connections, engagements, and proxies based on the extensions and then modify the docker compose file with the necessary networks, mounts, and environment variables to make the extended services work.\n\n## States\n\n- `telepresence compose up` will ensure that the extended services are in the correct state.\n- `telepresence compose create` is like `up`, but it will not start the containers, and therefore end any existing engagements once all the containers are created.\n- `telepresence compose stop` ends the engagements, but it keeps telepresence connected, because the existing containers use the `teleroute` network backed by that connection.\n- `telepresence compose down` will end the engagements, terminate the network, and quit telepresence.\n- `telepresence config` will detect if the project is started, if so, produce the extended project file. Otherwise, it will produce the original project file's canonical form.\n- `telepresence quit` will detect if a `telepresence compose` is running and, if so, issue a `telepresence compose down`.\n## Top-level Extension\n\nThe Top-level extension describes the connection and mount configurations that are used by the service extensions:\n\n| Name              | Description                                                                        | Type               | Default Value |\n|-------------------|------------------------------------------------------------------------------------|--------------------|---------------|\n| connections       | Connection configurations.                                                         | connection configs | empty         |\n| mounts            | Mount configurations.                                                              | mount configs      | empty         |\n\n### Connection Configuration\n\nThe `connections` field is a list of connection configurations. A single connection using the defaults from the current Kubernetes context is created when the list is empty.\n\nEach connection configuration is an object with the following fields:\n\n| Name              | Description                                                                        | Type    | Default Value                                                   |\n|-------------------|------------------------------------------------------------------------------------|---------|-----------------------------------------------------------------|\n| name              | The connection name                                                                | string  | generated based on the current Kubernetes context and namespace |\n| namespace         | The namespace to connect to                                                        | string  | The namespace configured in the current Kubernetes context      |\n| also-proxy        | Subnets that Telepresence should proxy in addition to the service and pod subnets. | CIDRs   | empty                                                           |\n| never-proxy       | Subnets that Telepresence should never proxy.                                      | CIDRs   | empty                                                           |\n| manager-namespace | The namespace in which the Telepresence Traffic Manager is running.                | string  | The name specified in the client config or \"ambassador\"         |\n| mapped-namespaces | Namespaces that Telepresence should map as DNS domains.                            | strings | empty                                                           |\n\n### Mount Configuration\n\nThe `mounts` field is a list of mount configurations that controls how the service extensions handle the volumes shared by the traffic-agent that the extended service will engage with. The mount configuration is an object with the following fields:\n\n| Name          | Description                                                                                              | Type   | Default Value                   |\n|---------------|----------------------------------------------------------------------------------------------------------|--------|---------------------------------|\n| volume        | Name of a Docker Compose volume. Mutually exclusive to volumePattern.                                    | string | empty                           |\n| volumePattern | Regular expression pattern matching one or several Docker Compose volumes. Mutually exclusive to volume. | string | empty                           |\n| policy        | \"local\", \"remote\", or \"remoteReadOnly\"                                                                   | string | determined by the traffic-agent |\n\nThe mount policy determines how the volume is mounted by Docker Compose.\n<dl>\n<dt>local</dt>\n<dd>The Docker Compose volume is not modified.</dd>\n<dt>remote</dt>\n<dd>The Docker Compose volume is modified to mount a remote volume without a read-only restriction. It might still be restricted by the remote volume's permissions.</dd>\n<dt>remoteReadOnly</dt>\n<dd>The Docker Compose volume is modified to mount a remote volume read-only restriction.</dd>\n</dl>\n\n## Service Extensions\n\nThe `x-tele` extension can be added to a Docker Compose service to extend the service's behavior. The extension must have a `type` field. Available types are:\n\n| Type                    | Local service Behavior                                          | Similar to               |\n|-------------------------|-----------------------------------------------------------------|--------------------------|\n| [connect](#connect)     | Service has access to the cluster's resources (DNS and routing) | `telepresence connect`   |\n| [proxy](#proxy)         | Replaced with a proxy for a service in the cluster              | N/A                      |\n| [ingest](#ingest)       | Acts as the handler for an ingested container the cluster       | `telepresence ingest`    |\n| [intercept](#intercept) | Acts as the handler for an intercepted service the cluster      | `telepresence intercept` |\n| [replace](#replace)     | Replaces a remote container in the cluster                      | `telepresence replace`   |\n| [wiretap](#wiretap)     | Receives wiretapped data from a service in the cluster          | `telepresence wiretap`   |\n\n### connect\n\nThe `connect` extension can be used as is, but it is also the base for all the other extensions. It will ensure that the extended docker compose service has access to the cluster's network\nand DNS by injecting a [teleroute](plugins.md#teleroute-network-plugin) network. The extension can have the following fields set:\n\n| Name       | Description                                                           | Type   | Default Value |\n|------------|-----------------------------------------------------------------------|--------|---------------|\n| connection | The name of a connection declared in the top-level `x-tele` extension | string | empty         |\n\nThe `connection` field is required only when more than one connection is declared in the top-level `x-tele` extension.\n\n### proxy\n\nThe `proxy` extension replaces the extended docker compose service with a proxy that redirects all traffic to a workload\nin the cluster. The extension can have the following fields set:\n\n| Name       | Description                                                           | Type    | Default Value                 |\n|------------|-----------------------------------------------------------------------|---------|-------------------------------|\n| connection | The name of a connection declared in the top-level `x-tele` extension | string  | empty                         |\n| name       | Name of the proxied remote service                                    | string  | name of the compose service   |\n| ports      | List of &lt;service-port&gt;:&lt;remote service-port&gt;> mappings    | strings | empty (route all ports as-is) |\n\n### ingest\n\nThe `ingest` ensures that the docker compose service shares the environment and volumes of a remote container in the cluster. The extension can have the following fields set:\n\n| Name       | Description                                                                   | Type    | Default Value               |\n|------------|-------------------------------------------------------------------------------|---------|-----------------------------|\n| connection | The name of a connection declared in the top-level `x-tele` extension         | string  | empty                       |\n| name       | Name of the remote workload (typically the deployment)                        | string  | name of the compose service |\n| container  | Name of the remote container                                                  | string  | empty                       |\n| to-pod     | Ports to forward from the local compose service to the remote pod's localhost | strings | empty                       |\n\nThe `container` field is required when more than one container is declared in the remote workload.\n\n### intercept\n\nThe `intercept` ensures that the docker compose service receives traffic from, and shares the environment and volumes of, a remote container in the cluster. The extension can have the following fields set:\n\n| Name             | Description                                                                                    | Type    | Default Value               |\n|------------------|------------------------------------------------------------------------------------------------|---------|-----------------------------|\n| connection       | The name of a connection declared in the top-level `x-tele` extension                          | string  | empty                       |\n| name             | Name of the intercept engagement                                                               | string  | name of the compose service |\n| httpFilters      | HTTP header filters. Only requests with matching headers will be intercepted                   | object  | empty                       |\n| httpPaths        | HTTP path filters. Only requests with matching paths will be intercepted. Exact path matching. | strings | empty                       |\n| httpPathPrefixes | HTTP path prefix filters. Only requests with matching path prefixes will be intercepted.       | strings | empty                       |\n| httpPathRegexps  | HTTP path regexp filters. Only requests with paths matching the regexp will be intercepted.    | strings | empty                       |\n| workload         | Name of the remote workload (typically the deployment)                                         | string  | name of the engagement      |\n| ports            | Service &lt;local port&gt;:&lt;service port&gt; to intercept                                   | strings | empty                       |\n| service          | Name of the remote service                                                                     | string  | empty                       |\n| to-pod           | Ports to forward from the local compose service to the remote pod's localhost                  | strings | empty                       |\n\n\nThe `service` field is optional as long as the given `ports` are unique.\nThe `workload` is useful when doing multiple intercepts on the same workload using different intercept names.\n\n\n### replace\n\nThe `replace` ensures that the docker compose service receives traffic from, and shares the environment and volumes of, a remote container in the cluster. The extension can have the following fields set:\n\n| Name       | Description                                                                   | Type    | Default Value               |\n|------------|-------------------------------------------------------------------------------|---------|-----------------------------|\n| connection | The name of a connection declared in the top-level `x-tele` extension         | string  | empty                       |\n| name       | Name of the remote workload (typically the deployment)                        | string  | name of the compose service |\n| container  | Name of the remote container                                                  | string  | empty                       |\n| ports      | Service &lt;local port&gt;:&lt;container port&gt; to replace                  | strings | empty                       |\n| to-pod     | Ports to forward from the local compose service to the remote pod's localhost | strings | empty                       |\n\n### wiretap\n\nThe `wiretap` ensures that the docker compose service receives wiretapped traffic from a remote service, and shares the environment and volumes (read-only) of, a remote container. The extension can have the following fields set:\n\n| Name             | Description                                                                                   | Type    | Default Value               |\n|------------------|-----------------------------------------------------------------------------------------------|---------|-----------------------------|\n| connection       | The name of a connection declared in the top-level `x-tele` extension                         | string  | empty                       |\n| name             | Name of the wiretapped workload  (typically the deployment)                                   | string  | name of the compose service |\n| httpFilters      | HTTP header filters. Only requests with matching headers will be wiretapped                   | object  | empty                       |\n| httpPaths        | HTTP path filters. Only requests with matching paths will be wiretapped. Exact path matching. | strings | empty                       |\n| httpPathPrefixes | HTTP path prefix filters. Only requests with matching path prefixes will be wiretapped.       | strings | empty                       |\n| httpPathRegexps  | HTTP path regexp filters. Only requests with paths matching the regexp will be wiretapped.    | strings | empty                       |\n| ports            | Service &lt;local port&gt;:&lt;service port&gt; to wiretap                                    | strings | empty                       |\n| service          | Name of the remote service                                                                    | string  | empty                       |\n| to-pod           | Ports to forward from the local compose service to the remote pod's localhost                 | strings | empty                       |\n\nThe `service` field is optional as long as the given `service ports` are unique.\n"
  },
  {
    "path": "docs/reference/config.md",
    "content": "\n---\ntitle: Laptop-side configuration\n---\n# Laptop-side configuration\n\nThere are a number of configuration values that can be tweaked to change how Telepresence behaves.\nThese can be set in three ways: globally, by a platform engineer with powers to deploy the Telepresence Traffic Manager, or locally by any user, either in the Telepresence configuration file `config.yml`, or as a Telepresence extension the Kubernetes configuration.\nOne important exception is the configuration of the of the traffic manager namespace, which, if it's different from the default of `ambassador`, [must be set](#manager) locally to be able to connect.\n\n## Global Configuration\n\nGlobal configuration is set at the Traffic Manager level and applies to any user connecting to that Traffic Manager.\nTo set it, simply pass in a `client` dictionary to the `telepresence helm install` command, with any config values you wish to set.\n\nThe `client` config supports values for [cluster](#cluster), [dns](#dns), [docker](#docker), [grpc](#grpc), [helm](#helm), [intercept](#intercept), [images](#images), [logLevels](#log-levels), [routing](#routing), and [timeouts](#timeouts).\n\nHere is an example configuration to show you the conventions of how Telepresence is configured:\n**note: This config shouldn't be used verbatim, since the registry `privateRepo` used doesn't exist**\n\n```yaml\nclient:\n  timeouts:\n    agentInstall: 1m\n    intercept: 10s\n  logLevels:\n    userDaemon: debug\n  images:\n    registry: privateRepo # This overrides the default docker.io/datawire repo\n    agentImage: tel2:$version$ # This overrides the agent image to inject when engaging with a workload\n  grpc:\n    maxReceiveSize: 10Mi\n    connectionTTL: 48h\n  dns:\n    includeSuffixes: [.private]\n    excludeSuffixes: [.se, .com, .io, .net, .org, .ru]\n    lookupTimeout: 30s\n  routing:\n    alsoProxySubnets:\n      - 1.2.3.4/32\n    neverProxySubnets:\n      - 1.2.3.4/32\n```\n\n### Cluster\nValues for `client.cluster` controls aspects on how client's connection to the traffic-manager.\n\n| Field                     | Description                                                                           | Type                                        | Default         |\n|---------------------------|---------------------------------------------------------------------------------------|---------------------------------------------|-----------------|\n| `defaultManagerNamespace` | The default namespace where the Traffic Manager will be installed.                    | [string][yaml-str]                          | ambassador      |\n| `mappedNamespaces`        | Namespaces that will be mapped by default.                                            | [sequence][yaml-seq] of [strings][yaml-str] | `[]`            |\n| `connectFromRootDaeamon`  | Make connections to the cluster directly from the root daemon.                        | [boolean][yaml-bool]                        | `true`          |\n| `agentPortForward`        | Let telepresence-client use port-forwards directly to agents                          | [boolean][yaml-bool]                        | `true`          |\n\n### Docker\nValues for the `client.docker` provides docker specific options.\n\n| Field            | Description                                                                           | Type                  | Default         |\n|------------------|---------------------------------------------------------------------------------------|-----------------------|-----------------|\n| `addHostGateway` | Add `--add-host host.docker.internal:host-gateway` when starting the daemon in docker | [boolean][yaml-bool]  | `true` on linux |\n| `telemount`      | Configuration of the image containing the telemount Docker volume plugin              | Image                 |                 |\n| `teleroute`      | Configuration of the image containing the teleroute Docker network plugin             | Image                 |                 |\n| `enableIPv4`     | Enable support for IPv4 networking                                                    | [boolean][yaml-bool]  | `true`          |\n| `enableIPv6`     | Enable support for IPv6 networking                                                    | [boolean][yaml-bool]  | `true`          |\n\n#### Image\n\nThe Image objects for `client.docker.telemount` and `client.docker.teleroute` provides information on how to download the\nimage from a registry.\n\nThe repositoryAPI must be capable of listing available tags if the tag is set to an empty string. The `ghcr.io/v2` API\ndoes not support this for anonymous users, hence the default tags.\n\n| Field         | Description                                      | Type               | Default               |\n|---------------|--------------------------------------------------|--------------------|-----------------------|\n| `registryAPI` | The URL used when connecting to the registry API | [string][yaml-str] | `ghcr.io/v2`          |\n| `registry`    | The name of the registry                         | [string][yaml-str] | `ghcr.io`             |\n| `namespace`   | The namespace of the component                   | [string][yaml-str] | `telepresenceio`      |\n| `repository`  | The name of the component registry               | [string][yaml-str] | `telemount/teleroute` |\n| `tag`         | The component tag                                | [string][yaml-str] | `0.1.6/0.3.0`         |\n\n### DNS\n\nThe `client.dns` configuration offers options for configuring the DNS resolution behavior in a client application or system. Here is a summary of the available fields:\n\nThe fields for `client.dns` are: `localIP`, `excludeSuffixes`, `includeSuffixes`, and `lookupTimeout`.\n\n| Field              | Description                                                                                                                                                         | Type                                        | Default                                            |\n|--------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|----------------------------------------------------|\n| `localIP`          | The address of the local DNS server.  This entry is only used on Linux systems that are not configured to use systemd-resolved.                                     | IP address [string][yaml-str]               | first `nameserver` mentioned in `/etc/resolv.conf` |\n| `excludeSuffixes`  | Suffixes for which the DNS resolver will always fail (or fallback in case of the overriding resolver). Can be globally configured in the Helm chart.                | [sequence][yaml-seq] of [strings][yaml-str] | `[\".arpa\", \".com\", \".io\", \".net\", \".org\", \".ru\"]`  |\n| `includeSuffixes`  | Suffixes for which the DNS resolver will always attempt to do a lookup.  Includes have higher priority than excludes. Can be globally configured in the Helm chart. | [sequence][yaml-seq] of [strings][yaml-str] | `[]`                                               |\n| `excludes`         | Names to be excluded by the DNS resolver                                                                                                                            | `[]`                                        |                                                    |\n| `mappings`         | Names to be resolved to other names (CNAME records) or to explicit IP addresses                                                                                     | `[]`                                        |                                                    |\n| `lookupTimeout`    | Maximum time to wait for a cluster side host lookup.                                                                                                                | [duration][go-duration] [string][yaml-str]  | 4 seconds                                          |\n| `recursionCheck`   | Enable DNS lookup recursion detection and avoidance.                                                                                                                | boolean                                     | false                                              |\n| `useComplexLookup` | Disable use of simplified but efficient A and AAAA lookups.                                                                                                         | [boolean][yaml-bool]                        | `false`                                            |\n\nHere is an example values.yaml:\n```yaml\nclient:\n  dns:\n    includeSuffixes: [.private]\n    excludeSuffixes: [.se, .com, .io, .net, .org, .ru]\n    localAddress: 172.12.0.53\n    lookupTimeout: 30s\n```\n\n#### Mappings\n\nAllows you to map hostnames to aliases or to IP addresses. This is useful when you want to use an alternative name for a service in the cluster, or when you want the DNS resolver to map a name to an IP address of your choice.\n\nIn the given cluster, the service named `postgres` is located within a separate namespace titled `big-data`, and it's referred to as `psql` :\n\n```yaml\ndns:\n  mappings:\n    - name: postgres\n      aliasFor: psql.big-data\n    - name: my.own.domain\n      aliasFor: 192.168.0.15\n```\n\n#### Exclude\n\nLists service names to be excluded from the Telepresence DNS server. This is useful when you want your application to interact with a local service instead of a cluster service. In this example, \"redis\" will not be resolved by the cluster, but locally.\n\n```yaml\ndns:\n  excludes:\n    - redis\n```\n\n#### recursionCheck\n\nThe `recursionCheck` is useful in situations when the cluster runs locally on the client and might have access to the client's DNS server. This is often the case when using clusters like Minikube or Kind, or have a Docker Desktop with Kubernetes enabled. The scenario is as follows:\n\n1. A request to resolve a name arrives to the Telepresence DNS server.\n2. Telepresence sends this request to the cluster's DNS server.\n3. The cluster's DNS server is unable to resolve a name, and in an attempt to resolve it globally, it sends it to its host.\n4. The host sends the request to the Telepresence DNS server.\n5. Telepresence, already in progress of resolving this name, will wait for the previous attempt to succeed.\n6. The original DNS request times out.\n\nThe final timeout is fairly slow. Enabling the `recursionCheck` will make it significantly faster because Telepresence will give up after a short timeout in step 5. The draw-back is that if multiple requests to resolve the same name arrive within a very short period of time, then there's a risk that Telepresence will not be able to distinguish the recursive calls from normal calls and respond with false timeouts.\n\n### Helm\nThe `client.helm` object contains options for the `telepresence helm` commands\n\n| Field       | Description                                                                                                  | Type               | Default                                         |\n|-------------|--------------------------------------------------------------------------------------------------------------|--------------------|-------------------------------------------------|\n| `chartURL`  | The URL used when downloading the Telepresence Helm chart when the version differs from the embedded version | [string][yaml-str] | `oci://ghcr.io/telepresenceio/telepresence-oss` |\n\n### Grpc\n\nAll traffic to and from the cluster is tunneled via gRPC over a kubernetes port-forward connection.\n\n| Field             | Description                                                                                   | Type                                      | Default  |\n|-------------------|-----------------------------------------------------------------------------------------------|-------------------------------------------|----------|\n| `connectionTTL`   | Max time that the traffic-manager or traffic-agent will keep an idle client connection alive  | [duration][go-duration]                   | `24h`    |\n| `maxReceiveSize`  | Max size of a gRCP message.                                                                   | Docker registry name [quantity][quantity] | `4Mi`    |\n\n### Images\nValues for `client.images` are strings. These values affect the objects that are deployed in the cluster,\nso it's important to ensure users have the same configuration.\n\nThese are the valid fields for the `client.images` key:\n\n| Field         | Description                                                                              | Type                                           | Default                             |\n|---------------|------------------------------------------------------------------------------------------|------------------------------------------------|-------------------------------------|\n| `registry`    | Docker registry to be used for installing the Traffic Manager and default Traffic Agent. | Docker registry name [string][yaml-str]        | `docker.io/datawire`                |\n| `agentImage`  | `$registry/$imageName:$imageTag` to use when installing the Traffic Agent.               | qualified Docker image name [string][yaml-str] | (unset)                             |\n| `clientImage` | `$registry/$imageName:$imageTag` to use locally when connecting with `--docker`.         | qualified Docker image name [string][yaml-str] | `$registry/ambassador-telepresence` |\n\n### Intercept\n\nThe `intercept` controls applies to how Telepresence will intercept the communications to replaced containers and intercepted services.\n\n| Field         | Description                                                                                                           | Type                 | Default    |\n|---------------|-----------------------------------------------------------------------------------------------------------------------|----------------------|------------|\n| `defaultPort` | controls which port is selected when no `--port` flag is given to the `telepresence intercept` command                | [int][yaml-int]      | 8080       |\n| `useFtp`      | Use fuseftp instead of sshfs when mounting remote file systems                                                        | [boolean][yaml-bool] | false      |\n| `mountsRoot`  | Directory that will be used as the root for all automatically generated mount directories (not applicable on windows) | [string][yaml-str]   | env:TMPDIR |\n\n### Log Levels\n\nValues for the `client.logLevels` fields are one of the following strings,\ncase-insensitive:\n\n- `trace`\n- `debug`\n- `info`\n- `warning` or `warn`\n- `error`\n\nFor whichever log-level you select, you will get logs labeled with that level and of higher severity.\n(e.g. if you use `info`, you will also get logs labeled `error`. You will NOT get logs labeled `debug`.\n\nThese are the valid fields for the `client.logLevels` key:\n\n| Field            | Description                                                                             | Type                                        | Default |\n|------------------|-----------------------------------------------------------------------------------------|---------------------------------------------|---------|\n| `userDaemon`     | Logging level to be used by the User Daemon (logs to connector.log)                     | [loglevel][slog-level] [string][yaml-str]   | info    |\n| `rootDaemon`     | Logging level to be used for the Root Daemon (logs to daemon.log)                       | [loglevel][slog-level] [string][yaml-str] | info    |\n| `kubeAuthDaemon` | Logging level to be used by the Kubernetes Authentication Daemon (logs to kubeauth.log) | [loglevel][slog-level] [string][yaml-str] | info    |\n| `cli`            | Logging level to be used by the CLI frontend (logs to cli.log)                          | [loglevel][slog-level] [string][yaml-str] | info    |\n\n### Routing\n\n#### AlsoProxySubnets\n\nWhen using `alsoProxySubnets`, you provide a list of subnets to be added to the TUN device.\nAll connections to addresses that the subnet spans will be dispatched to the cluster\n\nHere is an example values.yaml for the subnet `1.2.3.4/32`:\n```yaml\nclient:\n  routing:\n    alsoProxySubnets:\n      - 1.2.3.4/32\n```\n\n#### NeverProxySubnets\n\nWhen using `neverProxySubnets` you provide a list of subnets. These will never be routed via the TUN device,\neven if they fall within the subnets (pod or service) for the cluster. Instead, whatever route they have before\ntelepresence connects is the route they will keep.\n\nHere is an example kubeconfig for the subnet `1.2.3.4/32`:\n\n```yaml\nclient:\n  routing:\n    neverProxySubnets:\n      - 1.2.3.4/32\n```\n\n#### Using AlsoProxy together with NeverProxy\n\nNever proxy and also proxy are implemented as routing rules, meaning that when the two conflict, regular routing routes apply.\nUsually this means that the most specific route will win.\n\nSo, for example, if an `alsoProxySubnets` subnet falls within a broader `neverProxySubnets` subnet:\n\n```yaml\nneverProxySubnets: [10.0.0.0/16]\nalsoProxySubnets: [10.0.5.0/24]\n```\n\nThen the specific `alsoProxySubnets` of `10.0.5.0/24` will be proxied by the TUN device, whereas the rest of `10.0.0.0/16` will not.\n\nConversely, if a `neverProxySubnets` subnet is inside a larger `alsoProxySubnets` subnet:\n\n```yaml\nalsoProxySubnets: [10.0.0.0/16]\nneverProxySubnets: [10.0.5.0/24]\n```\n\nThen all of the `alsoProxySubnets` of `10.0.0.0/16` will be proxied, with the exception of the specific `neverProxySubnets` of `10.0.5.0/24`\n\nThese are the valid fields for the `client.routing` key:\n\n| Field                     | Description                                                                            | Type                    | Default            |\n|---------------------------|----------------------------------------------------------------------------------------|-------------------------|--------------------|\n| `alsoProxySubnets`        | Proxy these subnets in addition to the service and pod subnets                         | [CIDR][cidr]            |                    |\n| `neverProxySubnets`       | Do not proxy these subnets                                                             | [CIDR][cidr]            |                    |\n| `allowConflictingSubnets` | Give Telepresence precedence when these subnets conflict with other network interfaces | [CIDR][cidr]            |                    |\n| `recursionBlockDuration`  | Prevent recursion in VIF for this duration after a connect                             | [duration][go-duration] |                    |\n| `virtualSubnet`           | The CIDR to use when generating virtual IPs                                            | [CIDR][cidr]            | platform dependent |\n| `autoResolveConflicts`    | Auto resolve conflicts using a virtual subnet                                          | [bool][yaml-bool]       | true               |\n\n\n### Timeouts\n\nValues for `client.timeouts` are all durations either as a number of seconds\nor as a string with a unit suffix of `ms`, `s`, `m`, or `h`.  Strings\ncan be fractional (`1.5h`) or combined (`2h45m`).\n\nThese are the valid fields for the `timeouts` key:\n\n| Field                   | Description                                                                        | Type                                                                                                    | Default         |\n|-------------------------|------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|-----------------|\n| `agentInstall`          | Waiting for Traffic Agent to be installed                                          | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 2 minutes       |\n| `apply`                 | Waiting for a Kubernetes manifest to be applied                                    | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 1 minute        |\n| `clusterConnect`        | Waiting for cluster to be connected                                                | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 20 seconds      |\n| `connectivityCheck`     | Timeout used when checking if cluster is already proxied on the workstation        | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 500 ms (max 5s) |\n| `endpointDial`          | Waiting for a Dial to a service for which the IP is known                          | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 3 seconds       |\n| `roundtripLatency`      | How much to add  to the endpointDial timeout when establishing a remote connection | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 2 seconds       |\n| `intercept`             | Waiting for an intercept to become active                                          | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 30 seconds      |\n| `proxyDial`             | Waiting for an outbound connection to be established                               | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 5 seconds       |\n| `trafficManagerConnect` | Waiting for the Traffic Manager API to connect for port forwards                   | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 60 seconds      |\n| `trafficManagerAPI`     | Waiting for connection to the gPRC API after `trafficManagerConnect` is successful | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 15 seconds      |\n| `helm`                  | Waiting for Helm operations (e.g. `install`) on the Traffic Manager                | [int][yaml-int] or [float][yaml-float] number of seconds, or [duration][go-duration] [string][yaml-str] | 30 seconds      |\n\n## Local Overrides\n\nIn addition, it is possible to override each of these variables at the local level by setting up new values in local config files.\nThere are two types of config values that can be set locally: those that apply to all clusters, which are set in a single `config.yml` file, and those\nthat only apply to specific clusters, which are set as extensions to the `$KUBECONFIG` file.\n\n### Client Config\nTelepresence uses a `config.yml` file to store and change those configuration values that will be used by the Telepresence client.\nThe location of this file varies based on your OS:\n\n* macOS: `$HOME/Library/Application Support/telepresence/config.yml`\n* Linux: `$XDG_CONFIG_HOME/telepresence/config.yml` or, if that variable is not set, `$HOME/.config/telepresence/config.yml`\n* Windows: `%APPDATA%\\telepresence\\config.yml`\n\n### Values\n\nThe definitions of the values in the `config.yml` are identical to those values in the `client` config above, but without the top level `client` key.\n\nHere is an example configuration to show you the conventions of how Telepresence is configured:\n**note: This config shouldn't be used verbatim, since the registry `privateRepo` used doesn't exist**\n\n```yaml\ntimeouts:\n  agentInstall: 1m\n  intercept: 10s\nlogLevels:\n  userDaemon: debug\nimages:\n  registry: privateRepo # This overrides the default docker.io/datawire repo\n  agentImage: tel2:$version$ # This overrides the agent image to inject when engaging with a workload\ngrpc:\n  maxReceiveSize: 10Mi\n```\n\n\n## Workstation Per-Cluster Configuration\n\nConfiguration that is specific to a cluster connection can also be overriden per-workstation by modifying your `$KUBECONFIG` file.\nIt is recommended that you do not do this, and instead rely on upstream values provided to the Traffic Manager. This ensures\nthat all users that connect to the Traffic Manager will behave the same.\nAn important exception to this is the [`cluster.defaultManagerNamespace` configuration](#manager) which must be set locally.\n\n### Values\n\nThe definitions of the values in the Telepresence kubeconfig extension are identical to those values in the `config.yml` config. The values will be merged into the config and have higher\npriority when Telepresence is connected to the extended cluster.\n\nExample kubeconfig:\n```yaml\napiVersion: v1\nclusters:\n- cluster:\n    server: https://127.0.0.1\n    extensions:\n    - name: telepresence.io\n      extension:\n        cluster:\n          defaultManagerNamespace: staging\n        dns:\n          includeSuffixes: [.private]\n          excludeSuffixes: [.se, .com, .io, .net, .org, .ru]\n        routing:\n          neverProxy: [10.0.0.0/16]\n          alsoProxy: [10.0.5.0/24]\n  name: example-cluster\n```\n\n#### Manager\n\nThis is the one cluster configuration that cannot be set using the Helm chart because it defines how Telepresence  connects to\nthe Traffic manager. When not default, that setting needs to be configured in the workstation's kubeconfig for the cluster.\n\nThe `cluster.defaultManagerNamespace` key contains configuration for finding the `traffic-manager` that telepresence will connect to.\n\nHere is an example kubeconfig that will instruct telepresence to connect to a manager in namespace `staging`. The setting can be overridden using the Telepresence connect flag `--manager-namespace`.\n\nPlease note that the `cluster.defaultManagerNamespace` can be set in the `config.yml` too, but will then not be unique per cluster.\n\n```yaml\napiVersion: v1\nclusters:\n  - cluster:\n      server: https://127.0.0.1\n      extensions:\n        - name: telepresence.io\n          extension:\n            cluster:\n              defaultManagerNamespace: staging\n    name: example-cluster\n```\n\n[yaml-bool]: https://yaml.org/type/bool.html\n[yaml-float]: https://yaml.org/type/float.html\n[yaml-int]: https://yaml.org/type/int.html\n[yaml-seq]: https://yaml.org/type/seq.html\n[yaml-str]: https://yaml.org/type/str.html\n[quantity]: ../common/quantity.md\n[go-duration]: https://pkg.go.dev/time#ParseDuration\n[slog-level]: https://pkg.go.dev/log/slog#Level\n[cidr]: https://www.geeksforgeeks.org/classless-inter-domain-routing-cidr/\n"
  },
  {
    "path": "docs/reference/dns.md",
    "content": "---\ntitle: DNS resolution\nhide_table_of_contents: true\n---\n# DNS resolution\n\nThe Telepresence DNS resolver is dynamically configured to resolve names using the namespaces currently managed by the Traffic Manager. Processes running locally on the desktop will have network access to all services in the currently connected namespace by service-name only, and to other managed namespaces using service-name.namespace.\n\nSee this demonstrated below, using the [quick start's](../quick-start.md) sample app services.\n\nWe'll connect to a namespace in the cluster and list the services that can be intercepted.\n\n```\n$ telepresence connect --namespace default\n\n  Connecting to traffic manager...\n  Connected to context default, namespace default (https://<cluster-public-IP>)\n\n$ telepresence list\n\n  deployment web-app: ready to engage (traffic-agent not yet installed)\n  deployment emoji  : ready to engage (traffic-agent not yet installed)\n  deployment web    : ready to engage (traffic-agent not yet installed)\n\n$ curl web-app:80\n\n  <!DOCTYPE html>\n  <html>\n  <head>\n      <meta charset=\"UTF-8\">\n      <title>Emoji Vote</title>\n  ...\n```\n\nThe DNS resolver will also be able to resolve services using `<service-name>.<namespace>` regardless of what namespace the\nclient is connected to as long as the given namespace is among the set managed by the Traffic Manager.\n\n### Supported Query Types\n\nThe Telepresence DNS resolver is now capable of resolving queries of type `A`, `AAAA`, `CNAME`,\n`MX`, `NS`, `PTR`, `SRV`, and `TXT`.\n\nSee [Outbound connectivity](routing.md#dns-resolution) for details on DNS lookups.\n"
  },
  {
    "path": "docs/reference/docker-run.md",
    "content": "---\ntitle: Using Docker for engagements\ndescription: How a Telepresence engagement can run a Docker container with configured environment and volume mounts.\ntoc_min_heading_level: 2\ntoc_max_heading_level: 2\n---\n\n# Using Docker when engaging with workloads\n\n## Using command flags\n\n### The docker flag\nYou can start the Telepresence daemon in a Docker container on your laptop using the command:\n\n```console\n$ telepresence connect --docker\n```\n\n### The telepresence curl command\n\nThe network interface that is added when connecting using `telepresence connect --docker` will not be accessible\ndirectly from the host computer. It is confined to the telepresence daemon container.\n\nYou can use the `telepresence curl` command to curl your cluster resources. It will run curl in a docker container that\nshares the network and DNS of the daemon container.\n\n### The telepresence docker-run command\n\nYou can use the `telepresence docker-run` command to start a container that shares the network and DNS of the daemon\ncontainer.\n\n### The replace/ingest/intercept/wiretap --docker-run flag\n\nYou can use the `--docker-run` flag if you want your `replace`, `ingest`, `intercept`, or `wiretap` to use a local\nhandler that runs in a container. It will establish the engagement, run your container in the foreground, and then\nautomatically end the engagement when the container exits. It will also ensure that the container shares the network\nand DNS of the daemon container.\n\nPlease note that there your flags are divided into three groups when using `--docker-run`\n\n- General flags and arguments passed to the telepresence `replace`, `ingest`, `intercept`, or `wiretap` such as the\n  workload name or the port to intercept. The `--docker-run` flag is in itself an example of a general flag.\n- Flags and arguments passed to the `docker run` command such as `--env A=B`.\n- Flags and arguments passed to the container that is started.\n\nThe syntax of the command is\n```\n$ telepresence replace <general flags and args> -- <docker run flags and args> <image> <container flags and args>\n```\n\nIn essence, everything after the stand-alone double dash `--` is sent to the `docker run`.\n\nThe `--` separates flags intended for `telepresence replace/ingest/intercept/wiretap` from flags intended for `docker run`.\n\nIt's recommended that you always use the `--docker-run` in combination with a connection started with the\n`telepresence connect --docker`, because that makes everything less intrusive:\n\n- No admin user access is needed. Network modifications are confined to a Docker network.\n- There's no need for special filesystem mount software like MacFUSE or WinFSP. The volume mounts happen in the Docker engine.\n\nThe following happens under the hood when both flags are in use:\n\n- The local container will use a network controlled by the Teleroute network driver. This guarantees that the handler\n  can access the Telepresence VIF, and hence access the cluster.\n- The local container is configured to use DNS provided by the daemon container.\n- Volume mounts will be automatic and made using the Telemount Docker volume plugin so that all volumes exposed by the\n  targeted remote container are mounted on the local handler container.\n- The environment of the remote container becomes the environment of the local handler container.\n\n### The docker-build flag\n\nThe `--docker-build <docker context>` and the repeatable `docker-build-opt key=value` flags enable containers to be\nbuilt on the fly by the replace/ingest/intercept/wiretap command.\n\nWhen using `--docker-build`, the image name used in the argument list must be verbatim `IMAGE`. The word acts as a\nplaceholder and will be replaced by the ID of the image that is built. The presence of the word `IMAGE` is hence what\nseparates the flags and arguments sent to `docker run` from the ones sent to the container.\n\nThe `--docker-build` flag implies `--docker-run`.\n\n### The docker-debug flag\n\nThis flag is just like --docker-build, but allows a debugger to run inside the container with relaxed security.\n\n## Using docker-run flag without docker\n\nIt is possible to use `--docker-run` with a daemon running on your host, which is the default behavior of Telepresence. \n\nHowever, it isn't recommended since you'll be in a hybrid mode: while your handler runs in a container, the daemon will modify the host network, and if remote mounts are desired, they may require extra software. \n\nThe ability to use this special combination is retained for backward compatibility reasons. It might be removed in a future release of Telepresence.\n\nThe `--port` flag has slightly different semantics and can be used in situations when the local and container port must be different. This\nis done using `--port <local port>:<container port>`. The container port will default to the local port when using the `--port <port>` syntax.\n\n## Examples\n\nImagine you are working on a new version of your frontend service.  It is running in your cluster as a Deployment called `frontend-v1`. You use Docker on your laptop to build an improved version of the container called `frontend-v2`.  To test it out, use this command to run the new container on your laptop and start an intercept of the cluster service to your local container.\n\n```console\n$ telepresence connect --docker\n$ telepresence replace frontend-v1 --docker-run -- frontend-v2\n```\n\nNow, imagine that the `frontend-v2` image is built by a `Dockerfile` that resides in the directory `images/frontend-v2`. You can build and replace directly.\n\n```console\n$ telepresence replace frontend-v1 --docker-build images/frontend-v2 --docker-build-opt tag=mytag -- IMAGE\n```\n\n## Automatic flags\n\nTelepresence will automatically pass some relevant flags to Docker to connect the container with the remote container. Those flags are combined with the arguments given after `--` on the command line.\n\n- `--env-file <file>` Loads the remote environment\n- `--name intercept-<intercept name>-<intercept port>` Names the Docker container, this flag is omitted if explicitly given on the command line\n- `-v <local mount dir:docker mount dir>` Volume mount specification, see CLI help for `--docker-mount` flags for more info\n\nWhen used with a container based daemon:\n- `--rm` Mandatory, because the volume mounts cannot be removed until the container is removed.\n- `-v <telemount volume>:<docker mount dir>` Volume mount specifications propagated from the engaged container\n- `--network <name of containerized daemon>` Network is shared with the containerized daemon\n\nWhen used with a daemon that isn't container based:\n- `--dns-search tel2-search` Enables single label name lookups in the connected namespace\n- `-p <port:container-port>` The local port for the intercept and the container port\n"
  },
  {
    "path": "docs/reference/engagements/cli.md",
    "content": "---\ntitle: Configure workload engagements using CLI\n---\n\n# Configuring workload engagements using CLI\n\n## Specifying a namespace for an engagement\n\nThe namespace of the engaged workload is specified during connect using the `--namespace` option.\n\n```shell\ntelepresence connect --namespace myns\ntelepresence replace/ingest/intercept/wiretap hello\n```\n\n## Importing environment variables\n\nTelepresence can import the environment variables from the pod that is\nbeing engaged, see [this doc](../environment.md) for more details.\n\n## Creating an intercept\n\nThe following command will intercept HTTP requests using the header 'X-User: susan' bound to the service and proxy it to your laptop. This includes traffic coming through your ingress controller, so use this option carefully as to not disrupt production environments.\n\n```shell\ntelepresence intercept <deployment name> --http-header x-user=susan --port=<TCP port>\n```\n\nRun `telepresence list` to see the list of active intercepts.\n\n```console\n$ telepresence list\ndeployment dataprocessingnodeservice: intercepted\n   Intercept name: <deployment name>\n   State         : ACTIVE\n   Workload kind : Deployment\n   Intercepting  : 10.244.0.13 -> 127.0.0.1\n       8080 -> 8080 TCP\n   Intercepting  : Intercepting  : HTTP requests with header 'X-User: susan'\n```\n\nStart a service on your laptop that will receive the intercepted traffic on port 8080, for instance:\n```console\n$ python3 -m http.server 8080\n```\n\nUse curl to send requests to the intercepted service using the header 'X-User: susan'. The service running locally should be the one to respond to the request.\n```console\n$ curl -H \"X-User: susan\" http://<deployment name>/\nReply from service running on your local host\n```\n\nRun the same curl command again, but this time without the header 'X-User: susan'. Now the server running in the cluster should respond to the request.\n```console\n$ curl http://<deployment name>/\nReply from service running in your cluster\n```\n\nFinally, run `telepresence leave <name of intercept>` to stop the intercept.\n\nIf you want to change which port has been intercepted, you can create\na new intercept the same way you did above, and it will change which\nservice port is being intercepted.\n\n## Creating an intercept when multiple services match your workload\n\nOftentimes, there's a 1-to-1 relationship between a service and a\nworkload, so telepresence is able to auto-detect which service it\nshould intercept based on the workload you are trying to intercept.\nBut if you use something like\n[Argo](https://www.getambassador.io/docs/argo/latest/), there may be\ntwo services (that use the same labels) to manage traffic between a\ncanary and a stable service.\n\nFortunately, if you know which service you want to use when\nintercepting a workload, you can use the `--service` flag.  So in the\naforementioned example, if you wanted to use the `echo-stable` service\nwhen intercepting your workload, your command would look like this:\n\n```console\n$ telepresence intercept echo-rollout-<generatedHash> --port <local TCP port> --service echo-stable\nUsing ReplicaSet echo-rollout-<generatedHash>\nintercepted\n    Intercept name    : echo-rollout-<generatedHash>\n    State             : ACTIVE\n    Workload kind     : ReplicaSet\n    Destination       : 127.0.0.1:3000\n    Volume Mount Point: /var/folders/cp/2r22shfd50d9ymgrw14fd23r0000gp/T/telfs-921196036\n    Intercepting      : all TCP connections\n```\n\n## Intercepting multiple ports\n\nIt is possible to intercept more than one service and/or service port that are using the same workload. You do this\nby repeating the `--port` flag.\n\nLet's assume that we have a service `multi-echo` with the two ports `http` and `grpc`. They are both\ntargeting the same `multi-echo` deployment.\n\n```console\n$ telepresence intercept multi-echo-http --workload multi-echo --port 8080:http --port 8443:grpc\nUsing Deployment multi-echo\nintercepted\n    Intercept name         : multi-echo-http\n    State                  : ACTIVE\n    Workload kind          : Deployment\n    Intercepting           : 10.1.54.120 -> 127.0.0.1\n        8080 -> 8080 TCP\n        8443 -> 8443 TCP\n    Volume Mount Point     : /tmp/telfs-893700837\n```\n\n## Port-forwarding an intercepted container's sidecars\n\nSidecars are containers that sit in the same pod as an application\ncontainer; they usually provide auxiliary functionality to an\napplication, and can usually be reached at\n`localhost:${SIDECAR_PORT}`.  For example, a common use case for a\nsidecar is to proxy requests to a database, your application would\nconnect to `localhost:${SIDECAR_PORT}`, and the sidecar would then\nconnect to the database, perhaps augmenting the connection with TLS or\nauthentication.\n\nWhen intercepting a container that uses sidecars, you might want those\nsidecars' ports to be available to your local application at\n`localhost:${SIDECAR_PORT}`, exactly as they would be if running\nin-cluster.  Telepresence's `--to-pod ${PORT}` flag implements this\nbehavior, adding port-forwards for the port given.\n\n```console\n$ telepresence intercept <base name of intercept> --port=<local TCP port>:<servicePortIdentifier> --to-pod=<sidecarPort>\nUsing Deployment <name of deployment>\nintercepted\n    Intercept name         : <full name of intercept>\n    State                  : ACTIVE\n    Workload kind          : Deployment\n    Destination            : 127.0.0.1:<local TCP port>\n    Service Port Identifier: <servicePortIdentifier>\n    Intercepting           : all TCP connections\n```\n\nIf there are multiple ports that you need forwarded, simply repeat the\nflag (`--to-pod=<sidecarPort0> --to-pod=<sidecarPort1>`).\n\n## Intercepting headless services\n\nKubernetes supports creating [services without a ClusterIP](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services),\nwhich, when they have a pod selector, serve to provide a DNS record that will directly point to the service's backing pods.\nTelepresence supports intercepting these `headless` services as it would a regular service with a ClusterIP.\nSo, for example, if you have the following service:\n\n```yaml\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: my-headless\nspec:\n  type: ClusterIP\n  clusterIP: None\n  selector:\n    service: my-headless\n  ports:\n    - port: 8080\n      targetPort: 8080\n---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: my-headless\n  labels:\n    service: my-headless\nspec:\n  replicas: 1\n  serviceName: my-headless\n  selector:\n    matchLabels:\n      service: my-headless\n  template:\n    metadata:\n      labels:\n        service: my-headless\n    spec:\n      containers:\n        - name: my-headless\n          image: jmalloc/echo-server\n          ports:\n            - containerPort: 8080\n          resources: {}\n```\n\nYou can intercept it like any other:\n\n```console\n$ telepresence intercept my-headless --port 8080\nUsing StatefulSet my-headless\nintercepted\n    Intercept name    : my-headless\n    State             : ACTIVE\n    Workload kind     : StatefulSet\n    Destination       : 127.0.0.1:8080\n    Volume Mount Point: /var/folders/j8/kzkn41mx2wsd_ny9hrgd66fc0000gp/T/telfs-524189712\n    Intercepting      : all TCP connections\n```\n\n> [!IMPORTANT]\n> This utilizes an `initContainer` that requires `NET_ADMIN` capabilities.\n> If your cluster administrator has disabled them, you will be unable to use numeric ports with the agent injector.\n\n## Intercepting without a service\n\nYou can intercept a workload without a service by adding an annotation that informs Telepresence what container\nports that are eligable for intercepts. Telepresence will then inject a traffic-agent when the workload is\ndeployed, and you will be able to intercept the given ports as if they were service ports. The annotation is:\n\n```yaml\n      annotations:\n        telepresence.io/inject-container-ports: http\n```\n\nThe annotation value is a comma separated list of port identifiers consisting of either the name or the port number of a container\nport, optionally suffixed with `/TCP` or `/UDP`\n\n### Let's try it out!\n\n1. Deploy an annotation similar to this one to your cluster:\n\n   ```yaml\n   apiVersion: apps/v1\n   kind: Deployment\n   metadata:\n     name: echo-no-svc\n     labels:\n       app: echo-no-svc\n   spec:\n     replicas: 1\n     selector:\n       matchLabels:\n         app: echo-no-svc\n     template:\n       metadata:\n         labels:\n           app: echo-no-svc\n         annotations:\n           telepresence.io/inject-container-ports: http\n       spec:\n         automountServiceAccountToken: false\n         containers:\n           - name: echo-server\n             image: ghcr.io/telepresenceio/echo-server:latest\n             ports:\n               - name: http\n                 containerPort: 8080\n             env:\n               - name: PORT\n                 value: \"8080\"\n             resources:\n               limits:\n                 cpu: 50m\n                 memory: 8Mi\n   ```\n\n2. Connect telepresence:\n\n    ```console\n    $ telepresence connect\n    Launching Telepresence User Daemon\n    Launching Telepresence Root Daemon\n    Connected to context kind-dev, namespace default (https://127.0.0.1:36767)\n    ```\n\n3. List your intercept eligible workloads. If the annotation is correct, the deployment should show up in the list:\n\n   ```console\n   $ telepresence list\n   deployment echo-no-svc: ready to engage (traffic-agent not yet installed)\n   ```\n\n4. Start an intercept handler locally that will receive the incoming traffic. Here's an example using a simple python http service:\n\n   ```console\n   $ python3 -m http.server 8080\n   ```\n\n5. Create an intercept:\n\n   ```console\n   $ telepresence intercept echo-no-svc\n   Using Deployment echo-no-svc\n      Intercept name    : echo-no-svc\n      State             : ACTIVE\n      Workload kind     : Deployment\n      Destination       : 127.0.0.1:8080\n      Volume Mount Point: /tmp/telfs-3306285526\n      Intercepting      : all TCP connections\n      Address           : 10.244.0.13:8080\n   ```\n\nNote that the response contains an \"Address\" that you can curl to reach the intercepted pod. You will not be able to\ncurl the name \"echo-no-svc\". Since there's no service by that name, there's no DNS entry for it either.\n\n6. Curl the intercepted workload:\n\n   ```console\n   $ curl 10.244.0.13:8080\n   < output from your local service>\n   ```\n\n> [!IMPORTANT]\n> A service-less intercept utilizes an `initContainer` that requires `NET_ADMIN` capabilities.\n> If your cluster administrator has disabled them, you will only be able to intercept services using symbolic target ports.\n\n## Specifying the engagement traffic target\n\nBy default, it's assumed that your local app is reachable on `127.0.0.1` or on the IP of the local container that is\nrunning that app, and intercepted traffic will be sent to that address at the port given by `--port`. If you wish to\nchange this behavior and send traffic to a different address, you can use the `--address` parameter  to\n`telepresence intercept/replace/wiretap`. Say your machine is configured to respond to HTTP requests for an intercept\non a container named \"stoic_galois\". You would run this as:\n\n```console\n$ telepresence intercept my-service --address stoic-galois --port 8080\nUsing Deployment my-service\n   Intercept name: my-service\n   State         : ACTIVE\n   Workload kind : Deployment\n   Intercepting  : 127.0.0.1 -> stoic-galois\n       8080 -> 8080 TCP\n```\n\n## Replacing a running workload\n\nBy default, your application container continues to run while Telepresence intercepts its traffic. This can cause issues\nfor applications with ongoing background activities, such as consuming from a message queue.\n\nTo address this, the `telepresence intercept` command provides the `--replace` flag. When used, the Traffic Agent\nreplaces the application container within the pod. This ensures that the application itself is not running and avoids\nunintended side effects. The original application container is automatically restored once the intercept session ends.\n\n```console\n$ telepresence intercept my-service --port 8080 --replace\n   Intercept name         : my-service\n   State                  : ACTIVE\n   Workload kind          : Deployment\n   Destination            : 127.0.0.1:8080\n   Service Port Identifier: proxied\n   Volume Mount Point     : /var/folders/j8/kzkn41mx2wsd_ny9hrgd66fc0000gp/T/telfs-517018422\n   Intercepting           : all TCP connections\n```\n\n> [!NOTE]\n> Sidecars will not be stopped. Only the targeted container will be removed from the pod.\n"
  },
  {
    "path": "docs/reference/engagements/conflicts.md",
    "content": "---\ntitle: Dealing With Conflicting Engagements\n---\n\n# Dealing With Conflicting Engagements\n\n## The Problem\nAn organization may have several developers working on the same project, which in turn means that they may be engaging with the same workloads at the same time. Intercepting with unique http-header filters is often a good way to deal with this, but in some cases it may be necessary to use a global intercept or even to replace the entire container. Also, in some cases, perhaps a user intercepts using an http-header filter that is too broad and therefore causes conflicts with other users.\n\nSometimes the conflict is unavoidable. The user owning the first intercept must simply finish their work in order for others to continue. However, in other cases, perhaps that user has gone home for the day or got distracted by other tasks, not realizing that their intercept is still active and might cause problems for others.\n\n## The Solution\nTelepresence handles this situation in three ways:\n1. An inactive client has a limited time to live before the session is terminated. The termination is performed by the Traffic Manager but is similar to a user disconnecting using `telepresence quit`. This timeout is controlled by Helm chart value `grpc.connectionTTL` and defaults to 24 hours. \n2. An inactive client will normally retain ongoing intercepts for the duration of the `grpc.connectionTTL`, but the right to block other intercept attempts will be lost after a shorter timeout period controlled by Helm chart value `intercept.inactiveBlockTimeout`. This defaults to 10 minutes.\n3. In some cases, a client may be considered active even though no user is behind the keyboard (see [Active Client Semantics](#active-client-semantics) below). The command `telepresence revoke <intercept-id>` can be used to terminate such an intercept. Since this command is somewhat intrusive, it can only be performed by users that have RBAC permissions to get and update the \"traffic-manager\" configmap in the traffic-manager's namespace.\n\nThis means that a user may well close the lid on their laptop and come back the next day to continue working on their intercept, but only if that intercept didn't cause conflicts with other users. If it did, then it will be flagged as a conflict and the other user's attempt will instead succeed.\n\n## Active Client Semantics\nA client is considered active as long as the user interacts with Telepresence. Either by using the CLI or by issuing TCP network requests that trigger the creation of new tunnels to the cluster. Neither UDP network requests - which often originate from DNS requests that aren't related to Telepresence - nor inbound connections initiated from an intercepted pod, will make the client active.\n\n> [!NOTE]\n> The client will remain active if it runs processes that periodically use the Telepresence VIF to send TCP traffic to the cluster. \n"
  },
  {
    "path": "docs/reference/engagements/container.md",
    "content": "---\ntitle: Target a specific container\n---\n\n# Target a specific container\nA `telepresence replace` or `telepresence ingest` will always target a specific container, and the `--container` flag is\nmandatory when the workload has more than one container.\n\nA `telepresence intercept` or `telepresence wiretap` will ultimately target a specific port within a container. The port\nis usually determined by examining the relationship between the service's `targetPort` and the container's `containerPort`.\n\nIn certain scenarios, the container owning the intercepted port differs from the container the intercept\ntargets. This container's sole purpose is to route traffic from the service to the intended container,\noften using a direct localhost connection. Use the `--container` flag with the intercept in these scenarios.\n\n## No intercept\n\nConsider the following scenario:\n\n![no-intercept](../../images/secondary-no-intercept.png)\n\n## Standard Intercept\n\nDuring a replace, the Telepresence traffic-agent will redirect all traffic intended for the replaced container to the\nworkstation.  It will also make the environment and mounts for the **Nginx container** available, because it is\nconsidered to be the one targeted by the intercept.\n\nDuring an intercept, the Telepresence traffic-agent will redirect the `http` port to the workstation.\nIt will also make the environment and mounts for the **Nginx container** available, because it is\nconsidered to be the one targeted by the intercept.\n\n```console\n$ telepresence intercept myservice --port http\n```\n\n![normal-intercept](../../images/secondary-normal-intercept.png)\n\n## Intercept With --container\n\nThe `--container <name>` intercept flag is useful when the objective is to work with the App container\nlocally. While this option doesn't influence the port selection, it guarantees that the environment\nvariables and mounts propagated to the workstation originate from the specified container.\n\n```console\n$ telepresence intercept myservice --port http --container app\n```\n\n![container-intercept](../../images/secondary-container-intercept.png)\n"
  },
  {
    "path": "docs/reference/engagements/sidecar.md",
    "content": "---\ntitle: Traffic Agent Sidecar\n---\n# Traffic Agent Sidecar\n\nWhen replacing a container or intercepting a service, the Telepresence Traffic Manager ensures\nthat a Traffic Agent has been injected into the targeted workload.\nThe injection is triggered by a Kubernetes Mutating Webhook and will\nonly happen once. The Traffic Agent is responsible for making the environment and volumes available\non the developer's workstation, and also for redirecting traffic to it.\n\nWhen replacing a workload container, all traffic intended for it will be rerouted to the local workstation, unless\nlimited using the `--port` flag.\n\nWhen intercepting, all `tcp` and/or `udp` traffic to the targeted port is sent to the developer's workstation.\n\nThis means that both a `replace` and an `intercept` will affect all users of the targeted workload.\n\n## Supported workloads\n\nKubernetes has various\n[workloads](https://kubernetes.io/docs/concepts/workloads/).\nCurrently, Telepresence supports installing a\nTraffic Agent container on `Deployments`, `ReplicaSets`, `StatefulSets`, and `ArgoRollouts`. A Traffic Agent is\ninstalled the first time a user makes a `telepresence replace WORKLOAD`, `telepresence ingest WORKLOAD`,\n`telepresence intercept WORKLOAD`, `telepresence wiretap WORKLOAD`, or a `telepresence connect --proxy-via CIDR=WORKLAOD`.\n\nA Traffic Agent may also be installed up front by adding a `telepresence.io/inject-traffic-agent: enabled`\nannotation to the WORKLOADS pod template.\n\n### Sidecar injection\n\nThe actual installation of the Traffic Agent is performed by a mutating admission webhook that calls the agent-injector\nservice in the Traffic Manager's namespace.\n\nThe configuration for the sidecar, which is automatically generated, resides in the configmap `telepresence-agents`.\n\n### Uninstalling the Traffic Agent\n\nA Traffic Agent will normally remain in the workload's pods once it has been installed. It can be explicitly removed by\nissuing the command `telepresence uninstall WORKLOAD`. It will also be removed if its configuration is removed\nfrom the `telepresence-agents` configmap.\n\nRemoving the `telepresence-agents` configmap will effectively uninstall all injected Traffic Agents from the same\nnamespace.\n\n> [!NOTE]\n> Uninstalling will not work if the Traffic Agent is installed using the pod template annotation.\n\n### Disable Traffic Agent in a workload\n\nThe Traffic Agent installation can be completely disabled by adding a `telepresence.io/inject-traffic-agent: disabled`\nannotation to the WORKLOADS pod template. This will prevent all attempts to do anything with the workload that will\nrequire a Traffic Agent.\n\n### Disable workloads\n\nBy default, traffic-manager will observe `Deployments`, `ReplicaSets` and `StatefulSets`.\nEach workload used today adds certain overhead. If you are not engaging a specific workload type, you can disable it to reduce that overhead.\nThat can be achieved by setting the Helm chart values `workloads.<workloadType>.enabled=false` when installing the traffic-manager.\nThe following are the Helm chart values to disable the workload types:\n\n- `workloads.deployments.enabled=false` for `Deployments`,\n- `workloads.replicaSets.enabled=false` for `ReplicaSets`,\n- `workloads.statefulSets.enabled=false` for `StatefulSets`.\n\n### Enable ArgoRollouts\n\nIn order to use `ArgoRollouts`, you must pass the Helm chart value `workloads.argoRollouts.enabled=true` when installing the traffic-manager.\nIt is recommended to set the pod template annotation `telepresence.io/inject-traffic-agent: enabled` to avoid creation of unwanted\nrevisions.\n\n> [!NOTE]\n> While many of our examples use Deployments, they would also work on other supported workload types.\n"
  },
  {
    "path": "docs/reference/environment.md",
    "content": "---\ntitle: Environment variables\ndescription: \"How Telepresence can import environment variables from your Kubernetes cluster to use with code running on your laptop.\"\nhide_table_of_contents: true\n---\n\n# Environment variables\n\nTelepresence will import environment variables from the cluster container when engaging with it.\nYou can use these variables with the code running on your laptop.\n\nThere are several options available to do this:\n\n1. `telepresence replace <workload> --container <container> --env-file <filename>`\n\n   This will write the environment variables to a file. This file can be used when starting containers locally. The option `--env-syntax`\n   will allow control over the syntax of the file. Valid syntaxes are \"docker\", \"compose\", \"sh\", \"csh\", \"cmd\", and \"ps\" where \"sh\", \"csh\",\n   and \"ps\" can be suffixed with \":export\".\n\n2. `telepresence replace <workload> --container <container> --env-file <filename> --env-syntax=json`\n\n   This will write the environment variables to a JSON file. This file can be injected into other build processes.\n\n3. `telepresence replace <workload> --container <container> -- <command>`\n\n   This will run a command locally with the pod's environment variables set on your laptop.  Once the command quits the `replace` is stopped (as if `telepresence leave <workload>` was run).  This can be used in conjunction with a local server command, such as `python [FILENAME]` or `node [FILENAME]` to run a service locally while using the environment variables that were set on the pod via a ConfigMap or other means.\n\n   Another use would be running a subshell, Bash for example:\n\n4. `telepresence replace <workload> -- /bin/bash`\n\n   This would start the `replace` and then launch the subshell on your laptop with all the same variables set as on the pod.\n\n5. `telepresence replace <workload> --docker-run -- <container>`\n\n   This will ensure that the environment is propagated to the container. Will also work for `--docker-build` and `--docker-debug`.\n\n## Telepresence Environment Variables\n\nTelepresence adds some useful environment variables in addition to the ones imported from the engaged container:\n\n### TELEPRESENCE_ROOT\nDirectory where all remote volumes mounts are rooted. See [Volume Mounts](volume.md) for more info.\n\n### TELEPRESENCE_MOUNTS\nColon separated list of remotely mounted directories.\n\n### TELEPRESENCE_CONTAINER\nThe name of the targeted container. Useful when a pod has several containers, and you want to know which one that was engaged by Telepresence.\n"
  },
  {
    "path": "docs/reference/inside-container.md",
    "content": "---\ntitle: Running Telepresence inside a container\nhide_table_of_contents: true\n---\n# Running Telepresence inside a container\n\n## Run with the daemon and engagement handler in containers\n\nThe `telepresence connect` command now has the option `--docker`. This option tells telepresence to start the Telepresence daemon in a\ndocker container.\n\nRunning the daemon in a container brings many advantages. The daemon will no longer make modifications to the host's network or DNS, and\nit will not mount files in the host's filesystem. Consequently, it will not need admin privileges to run, nor will it need special software\nlike macFUSE or WinFSP to mount the remote file systems.\n\nThe engagement handler (the process that runs locally and optionally will receive intercepted traffic) must also be a docker container,\nbecause that is the only way to access the cluster network that the daemon makes available, and to mount the docker volumes needed.\n\n## Run everything in a container\n\nEnvironments like [GitHub Codespaces](https://docs.github.com/en/codespaces/overview) runs everything in a container. Your shell, the\ntelepresence CLI, and both its daemons. This means that the container must be configured so that it allows Telepresence to set up its\nVirtual Network Interface before you issue a `telepresence connect`.\n\nThere are several conditions that must be met.\n\n- Access to the `/dev/net/tun` device\n- The `NET_ADMIN` capability\n- If you're using IPv6, then you also need sysctl `net.ipv6.conf.all.disable_ipv6=0`\n\nThe Codespaces `devcontainer.json` will typically need to include:\n\n```json\n    \"runArgs\": [\n        \"--privileged\",\n        \"--cap-add=NET_ADMIN\",\n    ],\n```\n\n## Kubernetes auth plugins\n\nIf Kubernetes auth plugins are needed, they must be installed into the same container as Telepresence. Each auth plugin\nwill need a different approach.\n\n### AWS IAM Authenticator\n\n1. Install the AWS IAM Authenticator Go binary.\n\n```dockerfile\nFROM golang:alpine AS auth-builder\nRUN go install sigs.k8s.io/aws-iam-authenticator/cmd/aws-iam-authenticator@latest\n\n# Dockerfile with telepresence and its prerequisites\nFROM alpine\n\n# Install Telepresence prerequisites\nRUN apk add --no-cache curl iproute2 sshfs\n\n# Download and install the telepresence binary\nRUN curl -fL https://github.com/telepresenceio/telepresence/releases/download/v$version$/telepresence-linux-amd64 -o telepresence && \\\n   install -o root -g root -m 0755 telepresence /usr/local/bin/telepresence\n\nCOPY --from=auth-builder /go/bin/aws-iam-authenticator ./aws-iam-authenticator\nRUN install -o root -g root -m 0755 aws-iam-authenticator /usr/local/bin/aws-iam-authenticator && \\\n    rm aws-iam-authenticator\n```\n\n2. Ensure that the authenticator can reach your kubconfig and AWS configuration by mounting them into the container:\n\n```console\n$ docker run \\\n  --cap-add=NET_ADMIN \\\n  --device /dev/net/tun:/dev/net/tun \\\n  --network=host \\\n  -v ~/.kube/config:/root/.kube/config \\\n  -v ~/.aws:/root/.aws \\\n  -it --rm tp-in-docker\n```\n"
  },
  {
    "path": "docs/reference/monitoring.md",
    "content": "---\ntitle: Monitoring\n---\n\n# Monitoring\n\nTelepresence offers powerful monitoring capabilities to help you keep a close eye on your telepresence activities and traffic manager metrics.\n\n## Prometheus Integration\n\nOne of the key features of Telepresence is its seamless integration with Prometheus, which allows you to access real-time metrics and gain insights into your system's performance. With Prometheus, you can monitor various aspects of your traffic manager, including the number of active intercepts and users. Additionally, you can track consumption-related information, such as the number of intercepts used by your developers and how long they stayed connected.\n\nTo enable Prometheus metrics for your traffic manager, follow these steps:\n\n1. **Configure Prometheus Port**\n\n   First, you'll need to specify the Prometheus port by setting a new environment variable called `PROMETHEUS_PORT` for your traffic manager. You can do this by running the following command:\n\n   ```shell\n   telepresence helm upgrade --set-string prometheus.port=9090\n   ```\n\n2. **Validate the Prometheus Exposure**\n\n   After configuring the Prometheus port, you can validate its exposure by port-forwarding the port using Kubernetes:\n\n   ```shell\n   kubectl port-forward deploy/traffic-manager 9090:9090 -n ambassador\n   ```\n\n3. **Access Prometheus Dashboard**\n\n   Once the port-forwarding is set up, you can access the Prometheus dashboard by navigating to `http://localhost:9090` in your web browser:\n\n   Here, you will find a wealth of built-in metrics, as well as custom metrics (see below) that we have added to enhance your tracking capabilities.\n\n   | **Name**                    | **Type** | **Description**                                                               | **Labels**                               |\n   |-----------------------------|----------|-------------------------------------------------------------------------------|------------------------------------------|\n   | `telepresence_agent_count`               | Gauge    | Number of connected traffic agents.                                           |                                          |\n   | `telepresence_client_count`              | Gauge    | Number of connected clients.                                                  |                                          |\n   | `telepresence_active_intercept_count`    | Gauge    | Number of active intercepts.                                                  |                                          |\n   | `telepresence_session_count`             | Gauge    | Number of sessions.                                                           |                                          |\n   | `telepresence_tunnel_count`              | Gauge    | Number of tunnels.                                                            |                                          |\n   | `telepresence_tunnel_ingress_bytes`      | Counter  | Number of bytes tunnelled from clients.                                       |                                          |\n   | `telepresence_tunnel_egress_bytes`       | Counter  | Number of bytes tunnelled to clients.                                         |                                          |\n   | `telepresence_active_http_request_count` | Gauge    | Number of currently served HTTP requests.                                     |                                          |\n   | `telepresence_active_grpc_request_count` | Gauge    | Number of currently served gRPC requests.                                     |                                          |\n   | `telepresence_connect_count`             | Counter  | The total number of connects by user.                                         | `client`, `install_id`                   |\n   | `telepresence_connect_active_status`     | Gauge    | Flag to indicate when a connect is active. 1 for active, 0 for not active.    | `client`, `install_id`                   |\n   | `telepresence_intercept_count`           | Counter  | The total number of intercepts by user.                                       | `client`, `install_id`, `intercept_type` |\n   | `telepresence_intercept_active_status`   | Gauge    | Flag to indicate when an intercept is active. 1 for active, 0 for not active. | `client`, `install_id`, `workload`       |\n\n4. **Enable Scraping for Traffic Manager Metrics**\n   To ensure that these metrics are collected regularly by your Prometheus server and to maintain a historical record, it's essential to enable scraping. If you're using the default Prometheus configuration, you can achieve this by specifying specific pod annotations as follows:\n\n   ```yaml\n   template:\n     metadata:\n       annotations:\n         prometheus.io/path: /\n         prometheus.io/port: \"9090\"\n         prometheus.io/scrape: \"true\"\n   ```\n\n   These annotations instruct Prometheus to scrape metrics from the Traffic Manager pod, allowing you to track consumption metrics and other important data over time.\n\n## Grafana Integration\n\nGrafana plays a crucial role in enhancing Telepresence's monitoring capabilities. While the step-by-step instructions for Grafana integration are not included in this documentation, you have the option to explore the integration process. By doing so, you can create visually appealing and interactive dashboards that provide deeper insights into your telepresence activities and traffic manager metrics.\n\nMoreover, we've developed a dedicated Grafana dashboard for your convenience. Below, you can find sample screenshots of the dashboard, and you can access the JSON model for configuration:\n\n**JSON Model:**\n\nThis dashboard is designed to provide you with comprehensive monitoring and visualization tools to effectively manage your Telepresence environment.\n\n```json\n{\n  \"__inputs\": [\n    {\n      \"name\": \"DS_PROMETHEUS\",\n      \"label\": \"Prometheus\",\n      \"description\": \"\",\n      \"type\": \"datasource\",\n      \"pluginId\": \"prometheus\",\n      \"pluginName\": \"Prometheus\"\n    }\n  ],\n  \"__elements\": {},\n  \"__requires\": [\n    {\n      \"type\": \"panel\",\n      \"id\": \"barchart\",\n      \"name\": \"Bar chart\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"grafana\",\n      \"id\": \"grafana\",\n      \"name\": \"Grafana\",\n      \"version\": \"10.1.5\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"piechart\",\n      \"name\": \"Pie chart\",\n      \"version\": \"\"\n    },\n    {\n      \"type\": \"datasource\",\n      \"id\": \"prometheus\",\n      \"name\": \"Prometheus\",\n      \"version\": \"1.0.0\"\n    },\n    {\n      \"type\": \"panel\",\n      \"id\": \"stat\",\n      \"name\": \"Stat\",\n      \"version\": \"\"\n    }\n  ],\n  \"annotations\": {\n    \"list\": [\n      {\n        \"builtIn\": 1,\n        \"datasource\": {\n          \"type\": \"grafana\",\n          \"uid\": \"-- Grafana --\"\n        },\n        \"enable\": true,\n        \"hide\": true,\n        \"iconColor\": \"rgba(0, 211, 255, 1)\",\n        \"name\": \"Annotations & Alerts\",\n        \"type\": \"dashboard\"\n      }\n    ]\n  },\n  \"editable\": true,\n  \"fiscalYearStartMonth\": 0,\n  \"graphTooltip\": 0,\n  \"id\": null,\n  \"links\": [],\n  \"liveNow\": false,\n  \"panels\": [\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 6,\n        \"x\": 0,\n        \"y\": 0\n      },\n      \"id\": 5,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"10.1.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"telepresence_agent_count\",\n          \"instant\": true,\n          \"legendFormat\": \"__auto\",\n          \"range\": false,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of connected traffic agents\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 6,\n        \"x\": 6,\n        \"y\": 0\n      },\n      \"id\": 6,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"10.1.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"telepresence_client_count\",\n          \"instant\": true,\n          \"legendFormat\": \"__auto\",\n          \"range\": false,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of connected clients\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 6,\n        \"x\": 12,\n        \"y\": 0\n      },\n      \"id\": 7,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"10.1.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"telepresence_active_intercept_count\",\n          \"instant\": true,\n          \"legendFormat\": \"__auto\",\n          \"range\": false,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of active intercepts\",\n      \"type\": \"stat\"\n    },\n    {\n      \"datasource\": {\n        \"type\": \"prometheus\",\n        \"uid\": \"${DS_PROMETHEUS}\"\n      },\n      \"fieldConfig\": {\n        \"defaults\": {\n          \"color\": {\n            \"mode\": \"thresholds\"\n          },\n          \"mappings\": [],\n          \"thresholds\": {\n            \"mode\": \"absolute\",\n            \"steps\": [\n              {\n                \"color\": \"green\",\n                \"value\": null\n              },\n              {\n                \"color\": \"red\",\n                \"value\": 80\n              }\n            ]\n          }\n        },\n        \"overrides\": []\n      },\n      \"gridPos\": {\n        \"h\": 7,\n        \"w\": 6,\n        \"x\": 18,\n        \"y\": 0\n      },\n      \"id\": 8,\n      \"options\": {\n        \"colorMode\": \"value\",\n        \"graphMode\": \"area\",\n        \"justifyMode\": \"auto\",\n        \"orientation\": \"auto\",\n        \"reduceOptions\": {\n          \"calcs\": [\n            \"lastNotNull\"\n          ],\n          \"fields\": \"\",\n          \"values\": false\n        },\n        \"textMode\": \"auto\"\n      },\n      \"pluginVersion\": \"10.1.5\",\n      \"targets\": [\n        {\n          \"datasource\": {\n            \"type\": \"prometheus\",\n            \"uid\": \"${DS_PROMETHEUS}\"\n          },\n          \"editorMode\": \"code\",\n          \"exemplar\": false,\n          \"expr\": \"telepresence_session_count\",\n          \"instant\": true,\n          \"legendFormat\": \"__auto\",\n          \"range\": false,\n          \"refId\": \"A\"\n        }\n      ],\n      \"title\": \"Number of sessions\",\n      \"type\": \"stat\"\n    }\n  ],\n  \"refresh\": \"\",\n  \"schemaVersion\": 38,\n  \"style\": \"dark\",\n  \"tags\": [],\n  \"templating\": {\n    \"list\": []\n  },\n  \"time\": {\n    \"from\": \"now-30d\",\n    \"to\": \"now\"\n  },\n  \"timepicker\": {},\n  \"timezone\": \"\",\n  \"title\": \"Telepresence\",\n  \"uid\": \"d99c884a-8f4f-43f8-bd4e-bd68e47f100d\",\n  \"version\": 5,\n  \"weekStart\": \"\"\n}\n```\n"
  },
  {
    "path": "docs/reference/plugins.md",
    "content": "---\ntitle: Telepresence Docker Plugins\ntoc_min_heading_level: 2\ntoc_max_heading_level: 2\n---\n\n# Telepresence Docker Plug-ins\n\nA Telepresence client started with `telepresence connect --docker` will run in a Docker container. This is great because\nit means that the network that it creates, and the volumes that it mounts, will not interfere with the network and\nmounts on the workstation. Thanks to the Docker plugins Teleroute and Telemount, it also means that those resources are\navailable to other containers in the form of a Docker network and as Docker volumes.\n\n## Teleroute Network Plugin\nThe Telepresence Teleroute Docker network plugin is installed on demand and ensures that the cluster networks made\navailable by the daemon's virtual network interface (VIF) can be reached from other containers without interfering\nwith those container's network mode.\n\n### Technical brief\n\nThis is the sequence of events that occur when the user runs `telepresence connect --docker`:\n\n1. The Telepresence CLI checks if the Teleroute network driver plugin is present and installs it automatically[^1] if\n   it's not found.\n2. The Telepresence CLI starts the Telepresence daemon container using the default bridge network, so that it's assigned\n   an IP address that the Teleroute network plugin can connect to.\n3. The Telepresence daemon starts its `teleroute` gRPC service, and creates the \"br-daemon\" network bridge.\n4. The Telepresence CLI creates a network that uses the same name as the telepresence connection[^1]. It is configured to \n   use the teleroute network driver and to connect to the Telepresence daemon's `teleroute` gRPC service using the\n   daemon's IP address on the default bridge[^3].\n5. The Telepresence CLI connects the daemon to the new network. The network driver forwards the join request to the\n   daemon's `teleroute` service.\n6. In response, the `teleroute` service creates a veth-pair, sets the \"br-daemon\" bridge as `master` over one end of\n   that pair and moves the other end (the peer) to the network plugin's network namespace.\n7. The network plugin then moves the veth peer again. This time to the connecting container's namespace (in this\n   particular case, back to the daemon container).\n\nSteps similar to 5, 6, and 7 will happen when another container connects to the teleroute network. The only difference\nis that now, the plugin also adds routes to the container, enabling it to connect to the subnets exposed by the daemon's\nVIF:\n\n1. The network driver forwards the join request to the daemon's `teleroute` service.\n2. In response, the `teleroute` service creates a veth-pair, sets the \"br-daemon\" bridge as `master` over one end of\n   that pair and moves the other end (the peer) to the network plugin's network namespace.\n3. The network plugin then moves the veth peer again. This time to the connecting container's namespace.\n4. The network plugin adds the routes necessary to reach the daemon's VIF device via the daemon's IP to the connecting\n   container.\n\n#### DNS\n\nA container will get all routes necessary to reach cluster resources by connecting to the teleroute network. It will\nalso be configured to use the Docker internal DNS so that all containers on the same network can find each other. It is,\nhowever, not configured with a DNS that resolves names on the cluster side. One additional step is needed in order for\nthat to happen. The container must be started with `--dns <daemon container IP>` where the `<daemon container IP>` is\nthe IP that the daemon container was assigned when it connected to the teleroute network.\n\nThe container IP can be retrieved and then used together with the network like this:\n```console\n$ telepresence connect --docker --name blue\n$ DAEMON_IP=$(telepresence status --output json | jq -r .daemon.dns.local_address | sed -e 's/:53//')\n$ docker run --network blue --dns $DAEMON_IP --rm -it jonlabelle/network-tools\n```\n\nwhich is equivalent to using the Telepresence built-in `docker-run` command like this:\n\n```console\n$ telepresence connect --docker --name blue\n$ telepresence docker-run --rm -it jonlabelle/network-tools\n```\n\nWhen Telepresence starts a docker container, either by using `telepresence curl`, `telepresence docker-run` commands, or\nby using the `--docker-{run|build|debug}` flag in an engagement command, the following happens:\n\n1. The container will automatically be connected to the teleroute network.\n2. The container will get its DNS configured to use the DNS server exposed by the Telepresence daemon.\n\nPicture showing a Teleroute network connected to a daemon container and three local services\n\n![Architecture](../images/teleroute.svg)\n\n## Telemount Volume Plugin\n\nThe Telepresence Telemount Docker volume plugin is installed on demand and ensures that remote directories that are made\navailable by SFTP-servers in the traffic-agents can be mounted as Docker volumes. The driver is configured when a\ncontainerized Telepresence daemon is started, so that volumes can be created when Telepresence engages with a remote\ncontainer using `telepresence {ingest|intercept|replace|wiretap}`. These commands make a port available in the daemon\nthat connects the network driver with the remote SFTP-server.\n\nThis is the sequence of events that occur when the user runs `telepresence connect --docker`:\n\n1. A check is made if the Telemount volume plugin is present. If not, it is installed automatically[^4].\n2. The daemon configures itself to use a bridge mounter. This mounter is just a proxy that makes the port used by a\n   remote traffic-agent's SFTP server available on the daemon containers localhost.\n\nWhen Telepresence engages a container using the `--docker-{run|build|debug}` flag in an engagement command, the \nfollowing happens:\n\n1. A full list of volumes to be mounted is established. The command options are scanned for `-v`, `--volume` and\n   `--mount` flags, and those flags are then merged with the mounts propagated from the traffic-agent of the engaged\n   pod. The command options are given priority in this merge.\n2. A `docker create volume --driver=telemount` is executed for each volume in the list, passing the port number of the\n   proxied SFTP server to the volume plugin.\n3. The container is started with `-v` flags appointing the newly created volumes.\n4. The telemount performs SFTP mounts of the volumes, as needed.\n\nWhen the container engagement ends, the volumes are unmounted and removed, and the telepresence daemon closes the\nSFTP proxy.\n\n[^1]: The plugin registry, name, and tag can be fully configured using the `docker.teleroute` in the `config.yml`\n      configuration file.\n\n[^2]: The connection name is either given by the user using the `--name <name>` flag, or automatically generated from\n      the kubernetes context and cluster namespace, e.g. \"docker-desktop-default\"\n\n[^3]: The default port used for this connection is 4039, but it can be reconfigured using the `grpc.teleroutePort` in\n      the `config.yml` configuration file.\n\n[^4]: The plugin registry, name, and tag can be fully configured using the `docker.telemount` in the `config.yml`\nconfiguration file.\n"
  },
  {
    "path": "docs/reference/rbac.md",
    "content": "---\ntitle: RBAC\ntoc_min_heading_level: 2\ntoc_max_heading_level: 2\n---\n\n# Telepresence RBAC\nThe intention of this document is to provide a template for securing and limiting the permissions of Telepresence.\nThis documentation covers the full extent of permissions necessary to administrate Telepresence components in a cluster.\n\nThere are two general categories for cluster permissions with respect to Telepresence.  There are RBAC settings for a User and for an Administrator described above.  The User is expected to only have the minimum cluster permissions necessary to create a Telepresence [engagement](../howtos/engage.md), and otherwise be unable to affect Kubernetes resources.\n\nIn addition to the above, there is also a consideration of how to manage Users and Groups in Kubernetes which is outside of the scope of the document.  This document will use Service Accounts to assign Roles and Bindings.  Other methods of RBAC administration and enforcement can be found on the [Kubernetes RBAC documentation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) page.\n\n## Requirements\n\n- Kubernetes version 1.16+\n- Cluster admin privileges to apply RBAC\n\n## Editing your kubeconfig\n\nThis guide also assumes that you are utilizing a kubeconfig file that is specified by the `KUBECONFIG` environment variable.  This is a `yaml` file that contains the cluster's API endpoint information as well as the user data being supplied for authentication.  The Service Account name used in the example below is called tp-user.  This can be replaced by any value (i.e. John or Jane) as long as references to the Service Account are consistent throughout the `yaml`.  After an administrator has applied the RBAC configuration, a user should create a `config.yaml` in your current directory that looks like the following:\n\n```yaml\napiVersion: v1\nkind: Config\nclusters:\n- name: my-cluster # Must match the cluster value in the contexts config\n  cluster:\n    ## The cluster field is highly cloud-dependent.\ncontexts:\n- name: my-context\n  context:\n    cluster: my-cluster # Must match the name field in the clusters config\n    user: tp-user\nusers:\n- name: tp-user # Must match the name of the Service Account created by the cluster admin\n  user:\n    token: <service-account-token> # See note below\n```\n\nThe Service Account token will be obtained by the cluster administrator after they create the user's Service Account.  Creating the Service Account will create an associated Secret in the same namespace with the format `<service-account-name>-token-<uuid>`.  This token can be obtained by your cluster administrator by running `kubectl get secret -n ambassador <service-account-secret-name> -o jsonpath='{.data.token}' | base64 -d`.\n\nAfter creating `config.yaml` in your current directory, export the file's location to KUBECONFIG by running `export KUBECONFIG=$(pwd)/config.yaml`.  You should then be able to switch to this context by running `kubectl config use-context my-context`.\n\n## Administrating Telepresence\n\nTelepresence administration requires permissions for creating the `traffic-manager` [deployment](architecture.md#traffic-manager) which is typically\ndone by a full cluster administrator.\n\nOnce installed, the Telepresence Traffic Manager will run using the `traffic-manager` ServiceAccount. This account is\nset up differently depending on if the manager is installed using a dynamic or a static namespace selector.\n\n### Installation without, or with dynamic, namespace selection\n\nThe Traffic Manager will require cluster wide access to several resources when it lacks a namespace selector, or when it\nis configured with a dynamic namespace selector.\n\n### Traffic Manager Permissions\n\nThese are the permissions required by the `traffic-manager` account in such a configuration:\n\n```yaml\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: traffic-manager\n  namespace: ambassador\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: traffic-manager\nrules:\n  - apiGroups: [\"apps\"]\n    resources: [\"deployments\", \"replicasets\", \"statefulsets\"]\n    verbs: [\"get\", \"list\", \"watch\"]\n  - apiGroups: [\"\"]\n    resources: [\"namespaces\"]\n    verbs: [\"get\", \"list\"]\n  - apiGroups: [\"events.k8s.io\"]\n    resources: [\"events\"]\n    verbs: [\"get\", \"watch\"]\n  - apiGroups: [\"\"]\n    resources: [\"pods\"]\n    verbs: [\"get\", \"list\", \"watch\", \"patch\"] # patch not needed when agentInjector.enabled is set to false\n  - apiGroups: [\"networking.k8s.io\"]\n    resources: [\"servicecidrs\"]\n    verbs: [\"list\"]\n  \n  # If argoRollouts.enabled is set to true\n  - apiGroups: [\"argoproj.io\"]\n    resources: [\"rollouts\"]\n    verbs: [\"get\", \"list\", \"watch\"]\n\n  # When using podCIDRStrategy nodePodCIDRs\n  - apiGroups: [\"\"]\n    resources: [\"nodes\"]\n    verbs: [\"get\", \"list\", \"watch\"]\n\n  # The following is not needed when agentInjector.enabled is set to false\n  - apiGroups: [\"\"]\n    resources: [\"pods\"]\n    verbs: [\"patch\"]\n  - apiGroups: [\"apps\"]\n    resources: [\"deployments\", \"replicasets\", \"statefulsets\"]\n    verbs: [\"patch\"]\n  # If argoRollouts.enabled is set to true\n  - apiGroups: [\"argoproj.io\"]\n    resources: [\"rollouts\"]\n    verbs: [\"patch\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: traffic-manager\nsubjects:\n  - name: traffic-manager\n    kind: ServiceAccount\n    namespace: default\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  name: traffic-manager\n  kind: ClusterRole\n```\n\n\n### Installation with static namespace selection\n\nThe permissions required by the `traffic-manager` account in a statically namespaced configuration is very similar to\nthe ones used in a dynamic configuration, but a `Role`/`RoleBinding` will be installed in each managed namespace instead\nof the `ClusterRole`/`ClusterRoleBinding` pair.\n\n> [!NOTE]\n> One `ClusterRole/ClusterRoleBinding` will still be present that permits the traffic-manager to list the `servicecidr` resource.\n> That resource is cluster wide, so the following cluster wide rule is required:\n>\n> ```yaml\n>   - apiGroups: [\"networking.k8s.io\"]\n>     resources: [\"servicecidrs\"]\n>     verbs: [\"list\"]\n> ```\n\n## Telepresence Client Access\n\nA Telepresence client requires just a small set of RBAC permissions. The bare minimum to connect is the ability to\ncreate a port-forward to the traffic-manager.\n\nThe following configuration assumes that a ServiceAccount \"tp-user\" has been created in the traffic-manager's default\n\"ambassador\" namespace.\n\nIn order to connect, the client must resolve the traffic-manager service name into a pod-IP and set up a port-forward.\nThis requires the following Role/RoleBinding in the Traffic Manager's namespace.\n\n```yaml\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name:  traffic-manager-connect\n  namespace: ambassador\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"pods\"]\n    verbs: [\"get\", \"list\"]\n  - apiGroups: [\"\"]\n    resources: [\"services\"]\n    resourceNames: [\"traffic-manager\"]\n    verbs: [\"get\"]\n  - apiGroups: [\"\"]\n    resources: [\"pods/portforward\"]\n    verbs: [\"create\"]\n---\n\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: traffic-manager-connect\n  namespace: ambassador\nsubjects:\n  - kind: ServiceAccount\n    name: tp-user\n    namespace: ambassador\nroleRef:\n  kind: Role\n  name: traffic-manager-connect\n  apiGroup: rbac.authorization.k8s.io\n```\n\nOnce connected, it is desirable, but not necessary that the client can create port-forwards directly to Traffic Agents\nin the namespace that it is connected to. The lack of this permission will cause all traffic to be routed via the\nTraffic Manager, which will have a slightly negative impact on throughput.\n\nIt's recommended that the client also has the following permissions in a dynamic namespaces installation:\n\n```yaml\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name:  telepresence-ambassador\nrules:\n- apiGroups:\n  - \"\"\n  resources: [\"namespaces\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"get\"]\n\n  # Necessary if the client should be able to gather the pod logs\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\"]\n- apiGroups: [\"\"]\n  resources: [\"pods/log\"]\n  verbs: [\"get\"]\n\n  # All traffic will be routed via the traffic-manager unless a portforward can be created directly to a pod\n- apiGroups: [\"\"]\n  resources: [\"pods/portforward\"]\n  verbs: [\"create\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: telepresence-ambassador\nsubjects:\n  - kind: ServiceAccount\n    name: tp-user\n    namespace: ambassador\nroleRef:\n  kind: ClusterRole\n  name: telepresence-ambassador\n  apiGroup: rbac.authorization.k8s.io\n```\n\nThe corresponding configuration for a static namespace installation, for each namespaece that the client should be able\nto access:\n\n\n```yaml\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name:  telepresence-client\nrules:\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"get\"]\n\n  # Necessary if the client should be able to gather the pod logs\n- apiGroups: [\"\"]\n  resources: [\"pods\"]\n  verbs: [\"list\"]\n- apiGroups: [\"\"]\n  resources: [\"pods/log\"]\n  verbs: [\"get\"]\n\n  # All traffic will be routed via the traffic-manager unless a portforward can be created directly to a pod\n- apiGroups: [\"\"]\n  resources: [\"pods/portforward\"]\n  verbs: [\"create\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: telepresence-client\nsubjects:\n  - kind: ServiceAccount\n    name: tp-user\n    namespace: ambassador\nroleRef:\n  kind: Role\n  name: telepresence-client\n  apiGroup: rbac.authorization.k8s.io\n```\n\nThe user will also need the [Traffic Manager connect permission](#traffic-manager-permissions) described above.\n"
  },
  {
    "path": "docs/reference/restapi.md",
    "content": "# Telepresence RESTful API server\n\nTelepresence can run a RESTful API server on the local host, both on the local workstation and in a pod that contains a `traffic-agent`. The server currently has three endpoints. The standard `healthz` endpoint, the `consume-here` endpoint, and the `intercept-info` endpoint.\n\n## Enabling the server\nThe server is enabled by setting the `telepresenceAPI.port` to a valid port number in the [Telepresence Helm Chart](https://github.com/telepresenceio/telepresence/tree/release/v2/charts/telepresence). The value may be passed explicitly to Helm during the installation.\n\n## Querying the server\nOn the cluster's side, it's the `traffic-agent` of potentially intercepted pods that runs the server. The server can be accessed using `http://<TELEPRESENCE_API_HOST>:<TELEPRESENCE_API_PORT>/<some endpoint>` from the application container. Telepresence ensures that the container has the `TELEPRESENCE_API_HOST` and `TELEPRESENCE_API_PORT` environment variable set when the `traffic-agent` is installed. On the workstation, it is the `user-daemon` that runs the server. It uses the `TELEPRESENCE_API_PORT` that is conveyed in the environment of the intercept. This means that the server can be accessed the exact same way locally if the environment is propagated correctly to the interceptor process.\n\n## Endpoints\n\nThe `consume-here` and `intercept-info` endpoints are both intended to be queried with an optional path query and a set of headers, typically obtained from a Kafka message or similar. Telepresence provides the ID of the intercept in the environment variable `TELEPRESENCE_INTERCEPT_ID` during an intercept. This ID must be provided in a `x-telepresence-caller-intercept-id: = <ID>` header. Telepresence needs this to identify the caller correctly. The `<TELEPRESENCE_INTERCEPT_ID>` will be empty when running in the cluster, but it's harmless to provide it there too, so there's no need for conditional code.\n\nThere are three prerequisites to fulfill before testing The `consume-here` and `intercept-info` endpoints using `curl -v` on the workstation:\n1. An intercept must be active\n2. The \"/healthz\" endpoint must respond with OK\n3. The ID of the intercept must be known. It will be visible as `ID` in the output of `telepresence list --debug`.\n\n### healthz\nThe `http://localhost:<TELEPRESENCE_API_PORT>/healthz` endpoint should respond with status code 200 OK. If it doesn't, then something isn't configured correctly. Check that the `traffic-agent` container is present and that the `TELEPRESENCE_API_PORT` has been added to the environment of the application container and/or in the environment that is propagated to the interceptor that runs on the local workstation.\n\n#### test endpoint using curl\nA `curl -v` call can be used to test the endpoint when an intercept is active. This example assumes that the API port is configured to be 9980.\n```console\n$ curl -v localhost:9980/healthz\n* Host localhost:9980 was resolved.\n* IPv6: ::1\n* IPv4: 127.0.0.1\n*   Trying [::1]:9980...\n* Connected to localhost (::1) port 9980\n* using HTTP/1.x\n> GET /healthz HTTP/1.1\n> Host: localhost:9980\n> User-Agent: curl/8.11.1\n> Accept: */*\n> \n* Request completely sent off\n< HTTP/1.1 200 OK\n< Date: Fri, 03 Oct 2025 05:39:47 GMT\n< Content-Length: 0\n< \n* Connection #0 to host localhost left intact\n```\n\n### consume-here\n`http://<TELEPRESENCE_API_HOST>:<TELEPRESENCE_API_PORT>/consume-here` will respond with \"true\" (consume the message) or \"false\" (leave the message on the queue). When running in the cluster, this endpoint will respond with `false` if the headers match an ongoing intercept for the same workload because it's assumed that it's up to the intercept to consume the message. When running locally, the response is inverted. Matching headers means that the message should be consumed.\n\n#### test endpoint using curl\nAssuming that the API-server runs on port 9980, that the intercept was started with `--http-header x=y`, we can now check that the \"/consume-here\" returns \"true\" for given headers. Use `telepresence list --debug` to find the ID of the intercept.\n\n```console\n$ curl -v localhost:9980/consume-here -H 'x-telepresence-caller-intercept-id:9dbb0afa-38ec-48e2-a975-e664e579e197:apitest' -H 'x:y'\n* Host localhost:9980 was resolved.\n* IPv6: ::1\n* IPv4: 127.0.0.1\n*   Trying [::1]:9980...\n* Connected to localhost (::1) port 9980\n* using HTTP/1.x\n> GET /consume-here HTTP/1.1\n> Host: localhost:9980\n> User-Agent: curl/8.11.1\n> Accept: */*\n> x-telepresence-caller-intercept-id:9dbb0afa-38ec-48e2-a975-e664e579e197:apitest\n> x:y\n> \n* Request completely sent off\n< HTTP/1.1 200 OK\n< Content-Type: application/json\n< Date: Fri, 03 Oct 2025 05:45:16 GMT\n< Content-Length: 4\n< \n* Connection #0 to host localhost left intact\ntrue\n```\n\nIf you can run curl from the pod, you can try the exact same URL. The result should be \"false\" when there's an ongoing intercept. The `x-telepresence-caller-intercept-id` is not needed when the call is made from the pod.\n\n### intercept-info\n`http://<TELEPRESENCE_API_HOST>:<TELEPRESENCE_API_PORT>/intercept-info` is intended to be queried with an optional path query and a set of headers, typically obtained from a Kafka message or similar, and will respond with a JSON structure containing the two booleans `clientSide` and `intercepted`, and a `metadata` map which corresponds to the `--metadata` key pairs used when the intercept was created. This field is always omitted in case `intercepted` is `false`.\n\n#### test endpoint using curl\nAssuming that the API-server runs on port 9980, that the intercept was started with `--http-header x=y --metadata a=b --metadata b=c`, we can now check that the \"/intercept-info\" returns information for the given path and headers.\n```console\n$ curl -v localhost:9980/intercept-info -H 'x-telepresence-caller-intercept-id:9dbb0afa-38ec-48e2-a975-e664e579e197:apitest' -H 'x:y'\n* Host localhost:9980 was resolved.\n* IPv6: ::1\n* IPv4: 127.0.0.1\n*   Trying [::1]:9980...\n* Connected to localhost (::1) port 9980\n* using HTTP/1.x\n> GET /intercept-info HTTP/1.1\n> Host: localhost:9980\n> User-Agent: curl/8.11.1\n> Accept: */*\n> x-telepresence-caller-intercept-id:9dbb0afa-38ec-48e2-a975-e664e579e197:echo-easy\n> x:y\n> \n* Request completely sent off\n< HTTP/1.1 200 OK\n< Content-Type: application/json\n< Date: Fri, 03 Oct 2025 05:48:35 GMT\n< Content-Length: 38\n< \n* Connection #0 to host localhost left intact\n{\"intercepted\":true,\"clientSide\":true},\"metadata\":{\"a\":\"b\",\"b\":\"c\"}}\n```\n"
  },
  {
    "path": "docs/reference/route-controller.md",
    "content": "---\ntitle: Route Controller\ndescription: Prevent routing loops on local clusters by installing iptables FORWARD DROP rules for the service CIDR.\n---\n\n# Route Controller\n\n## Overview\n\nOn local Kubernetes clusters (Kind, minikube, k3d, Docker Desktop), deleted services can cause\nrouting loops when Telepresence is connected:\n\n1. A workstation process reaches a stale service ClusterIP through the TUN device.\n2. The TUN device forwards the connection to the traffic-agent.\n3. The traffic-agent dials the same stale ClusterIP.\n4. No kube-proxy rule exists for it, so the packet follows the node's default route and leaves the cluster.\n5. The packet returns to the workstation's TUN device and the loop repeats.\n\nThe root cause is that local-cluster nodes have no blackhole or reject route for the service CIDR.\nDeleted (or never-assigned) service IPs fall through to the node's default route and escape to the\nexternal network.\n\nThe **route-controller** is a DaemonSet that prevents these loops. It runs on every node\nwith host networking and `NET_ADMIN` privileges. At startup it discovers the cluster's service\nCIDRs and inserts a `DROP` rule into the iptables `FORWARD` chain for each CIDR. Any forwarded\npacket (i.e. pod traffic flowing through the host's network stack) destined for an IP that has no\nactive kube-proxy DNAT rule is dropped rather than escaping via the default route.\n\n### Why iptables FORWARD and not a kernel blackhole route?\n\nA kernel `RTN_BLACKHOLE` route for the service subnet would cause `connect()` and `sendmsg()` to\nfail at the socket level before any iptables hook can fire. This would break locally-generated\ntraffic such as the kube-apiserver calling the mutating webhook, or the route-controller itself\nreaching the Kubernetes API server.\n\nAn iptables `FORWARD` rule is only evaluated for traffic that has already been accepted into the\nhost's forwarding path. kube-proxy's PREROUTING DNAT rewrites the destination to a pod IP *before*\nthe FORWARD chain is reached, so rules for active services are completely unaffected.\n\n## Enabling the route controller\n\nThe route-controller is automatically enabled when the traffic-manager Helm chart is installed with\n`image.registry` set to `\"local\"` or a `\"localhost:*\"` address (the convention for local clusters).\nIt can also be force-enabled or force-disabled explicitly:\n\n```bash\n# Force-enable (e.g. on a remote cluster that exhibits the same problem)\nhelm upgrade --install traffic-manager charts/telepresence-oss \\\n  --set routeController.enabled=true\n\n# Force-disable (e.g. to opt out on a local cluster)\nhelm upgrade --install traffic-manager charts/telepresence-oss \\\n  --set routeController.enabled=false\n```\n\n### Helm values\n\n| Value | Default | Description |\n|---|---|---|\n| `routeController.enabled` | `null` | `null` = auto-detect, `true` = always enable, `false` = always disable |\n| `routeController.image.registry` | `\"\"` | Image registry (inherits `image.registry` when empty) |\n| `routeController.image.name` | `route-controller` | Image name |\n| `routeController.image.pullPolicy` | `\"\"` | Image pull policy (inherits `image.pullPolicy` when empty) |\n| `routeController.logLevel` | `\"\"` | Log level (inherits `logLevel` when empty) |\n| `routeController.serviceCIDRs` | `[]` | Explicit service CIDRs (see [Service CIDR discovery](#service-cidr-discovery)) |\n\n## Service CIDR discovery\n\nThe route-controller needs to know the cluster's service CIDRs to install the iptables rules.\nIt discovers them in priority order:\n\n1. **`SERVICE_CIDRS` environment variable** — set via `routeController.serviceCIDRs` in Helm values\n   (comma-separated list, e.g. `10.96.0.0/12`). This takes priority over all other methods.\n2. **Kubernetes ServiceCIDR API** (`networking.k8s.io/v1`, available in Kubernetes 1.33+) — the\n   controller lists `ServiceCIDR` objects from the cluster automatically.\n3. **Fallback** — if neither source is available the controller logs a warning and no iptables\n   rules are installed. Set `routeController.serviceCIDRs` explicitly in that case.\n\nFor clusters older than Kubernetes 1.33, set the service CIDR explicitly:\n\n```bash\nhelm upgrade --install traffic-manager charts/telepresence-oss \\\n  --set routeController.enabled=true \\\n  --set 'routeController.serviceCIDRs={10.96.0.0/12}'\n```\n\nTo find the service CIDR of an existing cluster you can inspect the kube-apiserver flags:\n\n```bash\nkubectl -n kube-system get pod kube-apiserver-$(kubectl get node -o jsonpath='{.items[0].metadata.name}') \\\n  -o jsonpath='{.spec.containers[0].command}' | tr ' ' '\\n' | grep service-cluster-ip-range\n```\n\n## Verifying the route controller\n\nAfter enabling, confirm the DaemonSet is running and the rules are in place:\n\n```bash\n# Check DaemonSet status\nkubectl -n ambassador get daemonset route-controller\n\n# View logs from one node\nkubectl -n ambassador logs -l app=route-controller --tail=50\n\n# On a Kind node: inspect iptables FORWARD rules\ndocker exec kind-control-plane iptables -L FORWARD -n | grep DROP\n```\n\n## RBAC\n\nThe route-controller uses a dedicated `ServiceAccount` with a `ClusterRole` that grants:\n\n- `get`, `list` on `networking.k8s.io/servicecidrs` (to auto-discover CIDRs on Kubernetes 1.33+)\n\nBoth resources are created automatically when the route-controller is enabled.\n"
  },
  {
    "path": "docs/reference/routing.md",
    "content": "---\ntitle: Connection Routing\ntoc_min_heading_level: 2\ntoc_max_heading_level: 2\n---\n\n# Connection Routing\n\n## DNS resolution\nWhen requesting a connection to a host, the IP of that host must be determined. Telepresence provides DNS resolvers to help with this task. There are currently four types of resolvers but only one of them will be used on a workstation at any given time. Common for all of them is that they will propagate a selection of the host lookups to be performed in the cluster. The selection normally includes all names ending with `.cluster.local` or a currently mapped namespace but more entries can be added to the list using the `includeSuffixes` or `mappings` option in the\n[cluster DNS configuration](config.md#dns) \n\n### Cluster side DNS lookups\nThe cluster side host lookup will be performed by a traffic-agent in the connected namespace, or by the traffic-manager if no such agent exists.\n\n### macOS resolver\nThis resolver hooks into the macOS DNS system by creating files under `/etc/resolver`. Those files correspond to some domain and contain the port number of the Telepresence resolver. Telepresence creates one such file for each of the currently mapped namespaces and `include-suffixes` option. The file `telepresence.local` contains a search path that is configured based on currently connected namespace so that single label names can be resolved correctly.\n\n### Linux systemd-resolved resolver\nThis resolver registers itself as part of telepresence's [VIF](tun-device.md) using `systemd-resolved` and uses the DBus API to configure domains and routes that corresponds to the connected namespace and the namespaces managed by the Traffic Manager.\n\n### Linux overriding resolver\nLinux systems that aren't configured with `systemd-resolved` will use this resolver. A Typical case is when running Telepresence [inside a docker container](inside-container.md). During initialization, the resolver will first establish a _fallback_ connection to the IP passed as `--dns`, the one configured as `local-ip` in the [local DNS configuration](config.md#dns), or the primary `nameserver` registered in `/etc/resolv.conf`. It will then use iptables to actually override that IP so that requests to it instead end up in the overriding resolver, which unless it succeeds on its own, will use the _fallback_.\n\n### Windows resolver\nThis resolver uses the DNS resolution capabilities of the [win-tun](https://www.wintun.net/) device in conjunction with [Win32_NetworkAdapterConfiguration SetDNSDomain](https://docs.microsoft.com/en-us/powershell/scripting/samples/performing-networking-tasks?view=powershell-7.2#assigning-the-dns-domain-for-a-network-adapter).\n\n### DNS caching\nThe Telepresence DNS resolver often changes its configuration. Telepresence will not flush the host's DNS caches. Instead, all records will have a short Time To Live (TTL) so that such caches evict the entries quickly. This causes increased load on the Telepresence resolver (shorter TTL means more frequent queries) and to cater for that, telepresence now has an internal cache to minimize the number of DNS queries that it sends to the cluster. This cache is flushed as needed without causing instabilities.\n\n## Routing\n\n### Subnets\nThe Telepresence `traffic-manager` service is responsible for discovering the cluster's service subnet and all subnets used by the pods. In order to do this, it needs permission to create a dummy service[^1] in its own namespace, and the ability to list, get, and watch nodes and pods. Most clusters will expose the pod subnets as `podCIDR` in the `Node` while others, like Amazon EKS, don't. Telepresence will then fall back to deriving the subnets from the IPs of all pods. If you'd like to choose a specific method for discovering subnets, or want to provide the list yourself, you can use the `podCIDRStrategy` configuration value in the [helm](../install/manager.md) chart to do that.\n\nThe complete set of subnets that the [VIF](tun-device.md) will be configured with is dynamic and may change during a connection's life cycle as new nodes arrive or disappear from the cluster. The set consists of what that the traffic-manager finds in the cluster, and the subnets configured using the [also-proxy](config.md#alsoproxysubnets) configuration option. Telepresence will remove subnets that are equal to, or completely covered by, other subnets.\n\n### Connection origin\nA request to connect to an IP-address that belongs to one of the subnets of the [VIF](tun-device.md) will cause a connection request to be made in the cluster. As with host name lookups, the request will originate from a traffic-agent in the connected namespace, of by the traffic-manager when no agent is present.\n\nThere are multiple reasons for doing this. One is that it is important that the request originates from the correct namespace. Example:\n\n```bash\ncurl some-host\n```\nresults in a http request with header `Host: some-host`. Now, if a service-mesh like Istio performs header-based routing, then it will fail to find that host unless the request originates from the same namespace as the host resides in. Another reason is that the configuration of a service mesh can contain very strict rules. If the request then originates from the wrong pod, it will be denied.\n\n## Recursion detection\nIt is common that clusters used in development, such as Minikube, Minishift or k3s, run on the same host as the Telepresence client, often in a Docker container. Such clusters may have access to host network, which means that both DNS and L4 routing may be subjected to recursion.\n\n### DNS recursion\nWhen a local cluster's DNS-resolver fails to resolve a hostname, it may fall back to querying the local host network. This means that the Telepresence resolver will be asked to resolve a query that was issued from the cluster. Telepresence must check if such a query is recursive because there is a chance that it actually originated from the Telepresence DNS resolver and was dispatched to the `traffic-manager`, or a `traffic-agent`.\n\nTelepresence handles this by sending one initial DNS-query to resolve the hostname \"tel2-recursion-check.kube-system\". If the cluster runs locally, and has access to the local host's network, then that query will recurse back into the Telepresence resolver. Telepresence remembers this and alters its own behavior so that queries that are believed to be recursions are detected and respond with an NXNAME record. Telepresence performs this solution to the best of its ability, but may not be completely accurate in all situations. There's a chance that the DNS-resolver will yield a false negative for the second query if the same hostname is queried more than once in rapid succession, that is when the second query is made before the first query has received a response from the cluster.\n\n##### Footnotes:\n[^1]: The error message from an attempt to create a service in a bad subnet contains the service subnet. The trick of creating a dummy service is currently the only way to get Kubernetes to expose that subnet.\n"
  },
  {
    "path": "docs/reference/tun-device.md",
    "content": "---\ntitle: Networking through Virtual Network Interface\nhide_table_of_contents: true\n---\n\n# Networking through Virtual Network Interface\n\nThe Telepresence daemon process creates a Virtual Network Interface (VIF) when Telepresence connects to the cluster. The VIF ensures that the cluster's subnets are available to the workstation. It also intercepts DNS requests and forwards them to the traffic-manager which in turn forwards them to engaged agents, if any, or performs a host lookup by itself.\n\n### TUN-Device\nThe VIF is a TUN-device, which means that it communicates with the workstation in terms of L3 IP-packets. The router will recognize UDP and TCP packets and tunnel their payload to the traffic-manager via its encrypted gRPC API. The traffic-manager will then establish corresponding connections in the cluster. All protocol negotiation takes place in the client because the VIF takes care of the L3 to L4 translation (i.e. the tunnel is L4, not L3).\n\n## Gains when using the VIF\n\n### Both TCP and UDP\nThe TUN-device is capable of routing both TCP and UDP traffic.\n\n### No SSH required\n\nThe VIF approach is somewhat similar to using `sshuttle` but without\nany requirements for extra software, configuration or connections.\nUsing the VIF means that only one single connection needs to be\nforwarded through the Kubernetes apiserver (à la `kubectl\nport-forward`), using only one single port.  There is no need for\n`ssh` in the client nor for `sshd` in the traffic-manager.  This also\nmeans that the traffic-manager container can run as the default user.\n\n#### sshfs without ssh encryption\nWhen a POD is engaged, and its volumes are mounted on the local machine, this mount is performed by [sshfs](https://github.com/libfuse/sshfs). Telepresence will run `sshfs -o slave` which means that instead of using `ssh` to establish an encrypted communication to an `sshd`, which in turn terminates the encryption and forwards to `sftp`, the `sshfs` will talk `sftp` directly on its `stdin/stdout` pair. Telepresence tunnels that directly to an `sftp` in the agent using its already encrypted gRPC API. As a result, no `sshd` is needed in client nor in the traffic-agent, and the traffic-agent container can run as the default user.\n\n### No Firewall rules\nWith the VIF in place, there's no longer any need to tamper with firewalls in order to establish IP routes. The VIF makes the cluster subnets available during connect, and the kernel will perform the routing automatically. When the session ends, the kernel is also responsible for cleaning up.\n"
  },
  {
    "path": "docs/reference/volume.md",
    "content": "---\ntitle: Volume mounts\nhide_table_of_contents: true\n---\n# Volume mounts\n\nVolume mounts are achieved using a Docker Volume plug-in and Docker volume mounts when connecting using `--docker` and using `--docker-run`. This page\ndescribes how mounts are achieved when running directly on the host.\n\nTelepresence supports locally mounting of volumes that are mounted to your Pods.  You can specify a command to run when starting the engagement, this could be a subshell or local server such as Python or Node.\n\n```\ntelepresence replace <workload> --mount=/tmp/ -- /bin/bash\n```\n\nIn this case, Telepresence replaces the remote container, mounts the Pod's volumes to locally to `/tmp`, and starts a Bash subshell.\n\nTelepresence can set a random mount point for you by using `--mount=true` instead, you can then find the mount point in the output of `telepresence list` or using the `$TELEPRESENCE_ROOT` variable.\n\n```\n$ telepresence replace <workload> --mount=true -- /bin/bash\nUsing Deployment <mysvc>\nreplaced\n    Container name    : <mycontainer>\n    State             : ACTIVE\n    Workload kind     : Deployment\n    Destination       : 127.0.0.1:<port>\n    Volume Mount Point: /var/folders/cp/2r22shfd50d9ymgrw14fd23r0000gp/T/telfs-988349784\n\nbash-3.2$ echo $TELEPRESENCE_ROOT\n/var/folders/cp/2r22shfd50d9ymgrw14fd23r0000gp/T/telfs-988349784\n```\n\n> [!NOTE]\n> `--mount=true` is the default if a mount option is not specified, use `--mount=false` to disable mounting volumes.\n\nWith either method, the code you run locally either from the subshell or from the `replace` command will need to be prepended with the `$TELEPRESENCE_ROOT` environment variable to utilize the mounted volumes.\n\nFor example, Kubernetes mounts secrets to `/var/run/secrets/kubernetes.io` (even if no `mountPoint` for it exists in the Pod spec).  Once mounted, to access these you would need to change your code to use `$TELEPRESENCE_ROOT/var/run/secrets/kubernetes.io`.\n\n> [!NOTE]\n> If using `--mount=true` without a command, you can use either [environment variable](environment.md) flag to retrieve the variable.\n"
  },
  {
    "path": "docs/reference/vpn.md",
    "content": "---\ntitle: Telepresence and VPNs\n---\n\nimport Platform from '@site/src/components/Platform';\n\n# Telepresence and VPNs\n\nTelepresence creates a virtual network interface (VIF) when it connects. This VIF is configured to route the cluster's \nservice and pod subnets so that the user can access resources in the cluster. It's not uncommon that the workstation\nwhere Telepresence runs already has network interfaces that route subnets that will overlap. Such\nconflicts must be resolved deterministically.\n\nUnless configured otherwise, Telepresence will resolve subnet conflicts by moving the cluster's subnet out of the way\nusing network address translation. For a majority of use-cases, this will be enough, but there are some\n[caveats](#caveats-when-using-vnat) to be aware of.\n\nFor more info, see the section on how to [avoid the conflict](#avoiding-the-conflict) below.\n\n## VPN Configuration\n\nLet's begin by reviewing what a VPN does and imagining a sample configuration that might come\nto conflict with Telepresence.\nUsually, a VPN client adds two kinds of routes to your machine when you connect.\nThe first serves to override your default route; in other words, it makes sure that packets\nyou send out to the public internet go through the private tunnel instead of your\nethernet or wifi adapter. We'll call this a `public VPN route`.\nThe second kind of route is a `private VPN route`. These are the routes that allow your\nmachine to access hosts inside the VPN that are not accessible to the public internet.\nGenerally speaking, this is a more circumscribed route that will connect your machine\nonly to reachable hosts on the private network, such as your Kubernetes API server.\n\nThis diagram represents what happens when you connect to a VPN, supposing that your\nprivate network spans the CIDR range: `10.0.0.0/8`.\n\n![VPN routing](../images/vpn-routing.jpg)\n\n## Kubernetes configuration\n\nOne of the things a Kubernetes cluster does for you is assign IP addresses to pods and services.\nThis is one of the key elements of Kubernetes networking, as it allows applications on the cluster\nto reach each other. When Telepresence connects you to the cluster, it will try to connect you\nto the IP addresses that your cluster assigns to services and pods.\nCluster administrators can configure, on cluster creation, the CIDR ranges that the Kubernetes\ncluster will place resources in. Let's imagine your cluster is configured to place services in\n`10.130.0.0/16` and pods in `10.132.0.0/16`:\n\n![VPN Kubernetes config](../images/vpn-k8s-config.jpg)\n\n# Telepresence conflicts\n\nWhen you run `telepresence connect` to connect to a cluster, it talks to the API server\nto figure out what pod and service CIDRs it needs to map in your machine. If it detects\nthat these CIDR ranges are already mapped by a VPN's `private route`, it will produce an\nerror and inform you of the conflicting subnets:\n\n```console\n$ telepresence connect\ntelepresence connect: error: connector.Connect: failed to connect to root daemon: rpc error: code = Unknown desc = subnet 10.43.0.0/16 overlaps with existing route \"10.0.0.0/8 via 10.0.0.0 dev utun4, gw 10.0.0.1\"\n```\n\nTelepresence offers three different ways to resolve this:\n\n- [Avoid the conflict](#avoiding-the-conflict) using the `--proxy-via` connect flag\n- [Allow the conflict](#allowing-the-conflict) in a controlled manner\n- [Use docker](#using-docker) to make telepresence run in a container with its own network config\n\n\n## Avoiding the conflict\n\nTelepresence can perform Virtual Network Address Translation (henceforth referred to as VNAT) of the cluster's subnets\nwhen routing them from the workstation, thus moving those subnets so that conflicts are avoided. Unless configured not\nto, Telepresence will use VNAT by default when it detects conflicts.\n\nVNAT is enabled by passing a `--vnat` flag (introduced in Telepresence 2.21) to`teleprence connect`. When using this\nflag, Telepresence will take the following  actions:\n\n- The local DNS-server will translate any IP contained in a VNAT subnet to a virtual IP.\n- All access to a virtual IP will be translated back to its original when routed to the cluster. \n- The container environment retrieved when using `replace`, `ingest`, `intercept`, or `wiretap` will be mangled, so that all IPs contained\n   in VNAT subnets are replaced with corresponding virtual IPs.\n\nThe `--vnat` flag can be repeated to make Telepresence translate more than one subnet.\n\n```console\n$ telepresence connect --vnat CIDR\n```\nThe CIDR can also be a symbolic name that identifies a well-known subnet or list of subnets:\n\n| Symbol    | Meaning                             |\n|-----------|-------------------------------------|\n| `also`    | All subnets added with --also-proxy |\n| `service` | The cluster's service subnet        | \n| `pods`    | The cluster's pod subnets.          | \n| `all`     | All of the above.                   |\n\n\n### Virtual Subnet Configuration\n\nTelepresence will use a special subnet when it generates the virtual IPs that are used locally. On a Linux or macOS\nworkstation, this subnet will be a class E subnet (not normally used for any other purposes). On Windows, the class E is\nnot routed, and Telepresence will instead default to `211.55.48.0/20`.\n\nThe default subnet used can be overridden in the client configuration.\n\nIn `config.yml` on the workstation:\n```yaml\nrouting:\n  virtualSubnet: 100.10.20.0/24\n```\n\nOr as a Helm chart value to be applied on all clients:\n```yaml\nclient:\n  routing:\n    virtualSubnet: 100.10.20.0/24\n```\n\n#### Example\n\nLet's assume that we have a conflict between the cluster's subnets, all covered by the CIDR `10.124.0.0/9` and a VPN\nusing `10.0.0.0/9`. We avoid the conflict using:\n\n```console\n$ telepresence connect --vnat all\n```\n\nThe cluster's subnets are now hidden behind a virtual subnet, and the resulting configuration will look like this:\n\n![VPN Telepresence](../images/vpn-vnat.jpg)\n\n### Proxying via a specific workload\n\nTelepresence is capable of routing all traffic to a VNAT to a specific workload. This is particularly useful when the\ncluster's DNS is configured with domains that resolve to loop-back addresses. This is sometimes the case when the\ncluster uses a mesh configured to listen to a loopback address and then reroute from there.\n\nThe `--proxy-via` flag (introduced in Telepresenc 2.19) is similar to `--vnat`, but the argument must be in the form\nCIDR=WORKLOAD. When using this flag, all traffic to the given CIDR will be routed via the given workstation.\n\nThe WORKLOAD is the deployment, replicaset, statefulset, or argo-rollout in the cluster whose traffic-agent will be used\nfor targeting the routed subnets.\n\n#### Example\n\nLet's assume that we have a conflict between the cluster's subnets, all covered by the CIDR `10.124.0.0/9` and a VPN\nusing `10.0.0.0/9`. We avoid the conflict using:\n\n```console\n$ telepresence connect --proxy-via all=echo\n```\n\nThe cluster's subnets are now hidden behind a virtual subnet, and all traffic is routed to the echo workload.\n\n### Caveats when using VNAT\n\nTelepresence may not accurately detect cluster-side IP addresses being used by services running locally on a workstation\nin certain scenarios. This limitation arises when local services obtain IP addresses from remote sources such as\ndatabases or configmaps, or when IP addresses are sent to it in API calls.\n\n### Disabling default VNAT\n\nThe default behavior of using VNAT to resolve conflicts can be disabled by adding the following to the client config. \n\nIn `config.yml` on the workstation:\n```yaml\nrouting:\n  autoResolveConflicts: false\n```\n\nOr as a Helm chart value to be applied on all clients:\n```yaml\nclient:\n  routing:\n    autoResolveConflicts: false\n```\n\nExplicitly allowing all conflicts will also effectively prevent the default VNAT behavior.\n\n## Allowing the conflict\n\nA conflict can be resolved by carefully considering what your network layout looks like, and then allow Telepresence to\noverride the conflicting subnets. Telepresence is refusing to map them, because mapping them could render certain hosts\nthat are inside the VPN completely unreachable. However, you (or your network admin) know better than anyone how hosts\nare spread out inside your VPN.\n\nEven if the private route routes ALL of `10.0.0.0/8`, it's possible that hosts are only being spun up in one of the\nsub-blocks of the `/8` space. Let's say, for example, that you happen to know that all your hosts in the VPN are bunched\nup in the first half of the space -- `10.0.0.0/9` (and that you know that any new hosts will only be assigned IP\naddresses from the `/9` block). In this case you can configure Telepresence to override the other half of this CIDR\nblock, which is where the services and pods happen to be.\n\nTo do this, all you have to do is configure the `client.routing.allowConflictingSubnets` flag in the Telepresence helm\nchart. You can do this directly via `telepresence helm upgrade`:\n\nIn `config.yml` on the workstation:\n```yaml\nrouting:\n  allowConflictingSubnets: 10.128.0.0/9\n```\n\nOr as a Helm chart configuration value to be applied on all clients:\n```yaml\nclient:\n  routing:\n    allowConflictingSubnets: 10.128.0.0/9\n```\n\nOr pass the Helm chart configuration using the `--set` flag\n```console\n$ telepresence helm upgrade --set client.routing.allowConflictingSubnets=\"{10.128.0.0/9}\"\n```\n\nThe end result of this (assuming an allowlist of `/9`) will be a configuration like this:\n\n![VPN Telepresence](../images/vpn-with-tele.jpg)\n\n### Using docker\n\nUse `telepresence connect --docker` to make the Telepresence daemon containerized, which means that it has its own\nnetwork configuration and therefore no conflict with a VPN. Read more about docker [here](docker-run.md).\n\n## Some helpful hints when dealing with conflicts\n\nWhen resolving a conflict by allowing it, you might want to validate that the routing is correct during the time when\nTelepresence is connected. One way of doing this is to retrieve the route for an IP in a conflicting subnet.\n\nThis example assumes that Telepresence detected a conflict with a VPN using subnet `100.124.0.0/16`, and that we then\ndecided to  allow a conflict in a small portion of that using allowConflictingSubnets `100.124.150.0/24`. Without\ntelepresence being connected, we check the route for the IP `100.124.150.45`, and discover  that it's running through a\nTailscale device.\n\n<Platform.Provider>\n<Platform.TabGroup>\n<Platform.MacOSTab>\n\n```console\n$ route -n get 100.124.150.45\n   route to: 100.64.2.3\ndestination: 100.64.0.0\n       mask: 255.192.0.0\n  interface: utun4\n      flags: <UP,DONE,CLONING,STATIC>\n recvpipe  sendpipe  ssthresh  rtt,msec    rttvar  hopcount      mtu     expire\n       0         0         0         0         0         0      1280         0\n```\n\nNote that in macOS it's difficult to determine what software the name of a virtual interface corresponds to -- `utun4`\ndoesn't indicate that it was created by Tailscale. One option is to look at the output of `ifconfig` before and after\nconnecting to your VPN to see if the interface in question is being added upon connection\n\n</Platform.MacOSTab>\n<Platform.GNULinuxTab>\n\n```console\n$ ip route get 100.124.150.45\n100.64.2.3 dev tailscale0 table 52 src 100.111.250.89 uid 0\n```\n\n</Platform.GNULinuxTab>\n<Platform.WindowsTab>\n\n```console\n$ Find-NetRoute -RemoteIPAddress 100.124.150.45\n\nIPAddress         : 100.102.111.26\nInterfaceIndex    : 29\nInterfaceAlias    : Tailscale\nAddressFamily     : IPv4\nType              : Unicast\nPrefixLength      : 32\nPrefixOrigin      : Manual\nSuffixOrigin      : Manual\nAddressState      : Preferred\nValidLifetime     : Infinite ([TimeSpan]::MaxValue)\nPreferredLifetime : Infinite ([TimeSpan]::MaxValue)\nSkipAsSource      : False\nPolicyStore       : ActiveStore\n\n\nCaption            : \nDescription        : \nElementName        : \nInstanceID         : ;::8;;;8<?:8BC9=<55<C55:8:8:8:55;\nAdminDistance      : \nDestinationAddress : \nIsStatic           : \nRouteMetric        : 0\nTypeOfRoute        : 3\nAddressFamily      : IPv4\nCompartmentId      : 1\nDestinationPrefix  : 100.124.150.45/32\nInterfaceAlias     : Tailscale\nInterfaceIndex     : 29\nInterfaceMetric    : 5\nNextHop            : 0.0.0.0\nPreferredLifetime  : 10675199.02:48:05.4775807\nProtocol           : NetMgmt\nPublish            : No\nState              : Alive\nStore              : ActiveStore\nValidLifetime      : 10675199.02:48:05.4775807\nPSComputerName     : \nifIndex            : 29\n```\n\n</Platform.WindowsTab>\n</Platform.TabGroup>\n</Platform.Provider>\n\nNow, run the same command with telepresence connected. The output should differ and instead show that the same IP Is\nrouted via the Telepresence Virtual Network. This should always be the case for an allowed conflict.\n\n> [!NOTE]\n> If you instead choose to avoid the conflict using VNAT, then the IP will be unaffected and still get routed via\n> Tailscale. The cluster resource using that IP will be available to you from another subnet, using another IP.\n"
  },
  {
    "path": "docs/release-notes.md",
    "content": "\n[comment]: # (Code generated by relnotesgen. DO NOT EDIT.)\n# <img src=\"images/logo.png\" height=\"64px\"/> Telepresence Release Notes\n## Version 2.27.2 <span style=\"font-size: 16px;\">(March  9)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix duplicate Section field in .deb package causing dpkg install failure](https://github.com/telepresenceio/telepresence/issues/4073)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>.deb</code> package control file contained a duplicate <code>Section</code> field, which caused <code>dpkg -i</code> to fail with a parsing error. The <code>Section</code> field was specified both as a top-level <code>nfpm</code> default and explicitly under <code>deb.fields</code>. Moved to the top-level <code>section</code> key and removed the <code>deb.fields</code> block entirely.\n</div>\n\n## Version 2.27.1 <span style=\"font-size: 16px;\">(March  8)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix duplicate Priority field in .deb package causing dpkg install failure](https://github.com/telepresenceio/telepresence/issues/4070)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>.deb</code> package control file contained a duplicate <code>Priority</code> field, which caused <code>dpkg -i</code> to fail with a parsing error. This was caused by <code>nfpm</code> adding <code>Priority: optional</code> by default while the build configuration also specified it explicitly under <code>deb.fields</code>.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix ingest command lookup when container name is not specified](https://github.com/telepresenceio/telepresence/issues/4067)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic manager now properly handles ingest lookups when using <code>telepresence ingest &lt;workload&gt; -- command</code> without specifying a container name. Previously, this would fail for workloads because the lookup couldn't find ingests with empty container names. The traffic manager now provides clearer error messages when a container name is required but not specified, and correctly resolves ingests for single-container workloads automatically.\n</div>\n\n## Version 2.27.0 <span style=\"font-size: 16px;\">(February 28)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add macOS package installer with root daemon as a system service](install/client)</div></div>\n<div style=\"margin-left: 15px\">\n\nA new macOS package installer (.pkg) is now available that installs Telepresence with the root daemon configured as a launchd service. This eliminates the need for elevated privileges when using Telepresence, as the daemon starts automatically at boot and runs in the background. Available for both Intel (amd64) and Apple Silicon (arm64) Macs.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add Linux package installers with root daemon as a system service](install/client)</div></div>\n<div style=\"margin-left: 15px\">\n\nNew Linux package installers (.deb for Debian/Ubuntu and .rpm for Fedora/RHEL) are now available that install Telepresence with the root daemon configured as a systemd service. This eliminates the need for elevated privileges when using Telepresence, as the service is enabled and started automatically during installation. Available for both amd64 and arm64 architectures.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add Windows installer with root daemon as a system service](install/client)</div></div>\n<div style=\"margin-left: 15px\">\n\nA new Windows installer (.exe) is now available that installs Telepresence with the root daemon configured as a Windows service. The installer bundles WinFSP and SSHFS-Win dependencies for volume mount support, adds Telepresence to the system PATH, and optionally installs the TelepresenceDaemon service. This eliminates the need for elevated privileges when using Telepresence. Currently available for amd64 architecture only due to dependency constraints.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add route-controller DaemonSet to prevent routing loops on local clusters](reference/route-controller)</div></div>\n<div style=\"margin-left: 15px\">\n\nA new optional <code>route-controller</code> DaemonSet can be deployed alongside the traffic-manager on local Kubernetes clusters (Kind, minikube, k3d, Docker Desktop) to prevent routing loops caused by deleted or non-existent service ClusterIPs. It installs an iptables <code>FORWARD</code> chain <code>DROP</code> rule for the service CIDR on every node, and adds per-IP kernel blackhole routes when a Service is deleted. Enable it with <code>routeController.enabled=true</code> in the Helm chart.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Automatic cache cleanup on version change</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence now tracks its version in a version.json file in the cache directory. When the CLI detects that the major.minor version differs from the running binary, it automatically quits running daemons and clears stale cache entries (preserving logs). This prevents issues caused by leftover cache files from a previous version. Patch and pre-release version changes do not trigger a cache cleanup.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Cluster DNS not injected into containers started by telepresence compose](https://github.com/telepresenceio/telepresence/issues/4053)</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen using <code>telepresence compose up</code>, cluster hostnames did not resolve inside the compose  container because the daemon DNS IP and the tel2-search DNS search domain were not being added to the generated compose spec. DNS and dns_search are now correctly set for all engaged compose services.\n</div>\n\n## Version 2.26.2 <span style=\"font-size: 16px;\">(February 14)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Dial 127.0.0.1 instead of 0.0.0.0 when connecting to local daemons](https://github.com/telepresenceio/telepresence/issues/4048)</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen a VPN routes all private network addresses, dialing 0.0.0.0 gets routed through the VPN instead of reaching the locally running daemon. This causes Telepresence to report that no daemon is running even though the processes are active and listening. The client now dials 127.0.0.1 explicitly, and the Docker-published daemon port is bound to 127.0.0.1 to match.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix HTTP intercepts via telepresence compose failing when httpFilters or httpPaths are set</div></div>\n<div style=\"margin-left: 15px\">\n\nThe compose extension set HeaderFilters and PathFilters on the intercept spec but left the mechanism as \"tcp\". This caused the traffic manager to reject the intercept with \"global TCP/UDP intercepts are disabled\". The mechanism is now correctly switched to \"http\" when any HTTP filters are specified, matching the behavior of the CLI intercept command.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix \"root daemon is embedded\" error on Windows elevated terminals](https://github.com/telepresenceio/telepresence/issues/4049)</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen running Telepresence in an elevated (administrator) terminal on Windows, commands like connect and loglevel failed with \"root daemon is embedded\". The user daemon now correctly delegates to the in-process root daemon session instead of returning an error.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix h2c (HTTP/2 cleartext) prior knowledge not working on transport](https://github.com/telepresenceio/telepresence/issues/4056)</div></div>\n<div style=\"margin-left: 15px\">\n\nSince Go 1.24, having both HTTP1 and UnencryptedHTTP2 enabled on an HTTP transport causes Go to default to HTTP/1.1 instead of using h2c prior knowledge. The transport now explicitly disables HTTP1 when UnencryptedHTTP2 is enabled, fixing h2c communication for both the default forwarding handler and intercepted traffic.\n</div>\n\n## Version 2.26.1 <span style=\"font-size: 16px;\">(January 26)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add support for \"warning\" as an alias for \"warn\" in log levels](https://github.com/telepresenceio/telepresence/issues/4043)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe \"warning\" alias for \"warn\" was the only acceptable value in the Helm chart, yet the traffic manager didn't accept it. This is now fixed so that both names are accepted by the Helm chart and by the traffic manager.\n</div>\n\n## Version 2.26.0 <span style=\"font-size: 16px;\">(January 23)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add ability for cluster admins to revoke other users' intercepts.](reference/engagements/conflicts)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic-manager can now execute commands defined in the traffic-manager ConfigMap. As a result, clients that are authorized to\nupdate this ConfigMap can issue those commands.\n\nThis mechanism lays the groundwork for distinguishing administrators from regular users in Telepresence. Administrators can be\ngranted RBAC permissions that allow them to update the traffic-manager ConfigMap, while regular users cannot.\n\nThe new command, `telepresence revoke <intercept-id>`, uses this mechanism to revoke the intercept associated with the specified\nID.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add support for overriding intercepts owned by inactive clients](reference/engagements/conflicts)</div></div>\n<div style=\"margin-left: 15px\">\n\nIntroduce the Helm chart setting `intercept.inactiveBlockTimeout` that controls the maximum amount of time an intercept may be held by a client that is unreachable or inactive. Once this timeout is exceeded, the intercept no longer blocks conflicting intercepts and may be automatically removed when another client attempts to create a conflicting intercept.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add support for sudo-rs</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence now supports running the root daemon using `sudo-rs`. This is necessary on Ubuntu where `sudo-rs` has become the default for `sudo`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add configuration to disable global TCP/UDP intercepts](reference/cluster_config#restricting_global_intercepts)</div></div>\n<div style=\"margin-left: 15px\">\n\nA new Helm chart configuration option `intercept.allowGlobalIntercepts` has been added to control whether global TCP/UDP intercepts are permitted. When set to `false`, only HTTP intercepts with header or path filters are allowed, preventing users from creating global intercepts that block other developers from intercepting the same port. This is particularly useful in shared development environments where multiple developers need to work on the same service simultaneously. The setting defaults to `true` to maintain full backward compatibility with existing deployments. When a user attempts to create a global intercept while the setting is disabled, they receive a helpful error message suggesting the use of `--http-header` or `--http-path-*` flags for HTTP-filtered intercepts.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Enhanced Traffic Manager Startup Reliability</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Traffic Manager deployment now includes a `startupProbe` that accurately detects when the service is fully initialized and\nready to handle traffic. This enhancement brings the following benefits:\n\n- **Prevents Premature Traffic Routing**: The manager only reports itself as ready after all configurations are loaded,\neliminating potential race conditions\n- **Smoother Upgrades and Rollouts**: Deployment orchestration tools can reliably determine when the Traffic Manager is\noperational, improving overall installation stability\n\nThis change is particularly beneficial in large clusters or complex networking environments where initialization may take longer\nthan expected.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Support customizable daemon config file</div></div>\n<div style=\"margin-left: 15px\">\n\nThe config file for Telepresence is now configurable through the command-line flag `--config`. The `--config <agent config>` flag of the `telepresence genyaml` command was renamed to `--agent` to avoid confusion.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Support customizable daemon log file paths</div></div>\n<div style=\"margin-left: 15px\">\n\nLog file paths for the Telepresence daemons are now configurable through the command-line flag `--logfile` that denotes a custom log file location or redirect of the log output to stdout/stderr. Two new log-level configuration entries for `cli` and `kubeAuthDaemon` are also introduced, expanding the existing log-level controls beyond just `userDaemon` and `rootDaemon`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to exclude or include modifications made by other injectors when injecting the traffic agent.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Traffic Agent now has a new configuration option `agentInjector.mutationAware` that can be set to `false` to exclude modifications made by other injectors when injecting the traffic agent. Setting `agentInjector.mutationAware=true` requires `agentInjector.webhook.reinvocationPolicy=IfNeeded`. The default setting is `true`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to disable the Traffic Agent's HTTP2/Clear-Text probing.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Traffic Agent now has a new configuration option `agent.enableH2cProbing` that can be set to `false` to disable the HTTP2/Clear-Text probing. The default setting is `true` to preserve backwards compatibility, but this will be changed to `false` in a future release.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to configure the Traffic Agent's retry interval for watching intercepts.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Traffic Agent's retry interval when it establishes its watcher for intercepts is now configurable using the Helm chart value `agent.watchRetryInterval`. The default retry interval was also increased from 2 seconds to 10 seconds to improve resilience when connections to the traffic manager are lost.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Make traffic-agent consumption metrics reporting optional.</div></div>\n<div style=\"margin-left: 15px\">\n\nMetrics reporting for the traffic-agent can now be optionally enabled or disabled via the `agent.enableConsumptionMetrics` Helm chart value. The traffic-agent metrics will also be completely disabled when the Helm chart value `prometheus.port` is unset or zero. This change is intended to reduce the amount of unnecessary traffic generated by the traffic-agent when consumption metrics reporting is of no interest to the user.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Improved efficiency of traffic manager map updates.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe watchable map has been refactored into a client/server model that supports delta-based updates. Where supported, gRPC now transmits only incremental changes instead of full snapshots, significantly reducing payload sizes. This improvement is especially important for large clusters, where full snapshots can be sizable and costly to transmit. Full snapshot streaming remains available as a backward-compatible fallback for clients that do not support delta methods.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Use TCP/IP instead of Unix sockets for all communication between local processes.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence now uses TCP/IP sockets for all communication between local processes. This change reduces the risk of socket-related errors caused by lingering sockets from previous Telepresence sessions. It also eliminates the difficulties of using Unix sockets for communication between a system service and user processes on Windows.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Don't allow connect with --docker when client is configured with intercept.useFtp=true</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `--docker` flag is not allowed when the client is configured with `intercept.useFtp=true` and an error is now generated\ninstantly by the `telepresence connect --docker` command.\n\nThe docker volume plugin cannot use FTP because it requires two ports: a fixed control port that Telepresence can proxy, and a\ndynamic data port (randomly chosen during connection) that Telepresence cannot proxy on-demand. The port-forwarder only forwards\npre-configured ports and doesn't understand FTP's protocol. Consequently, FTP isn't allowed in this scenario. If it was, then when\nan FTP server tells the client to use an unpredictable second port for file transfers, Telepresence would block it—causing the\nconnection to fail every time.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Better names for the Telepresence Daemons</div></div>\n<div style=\"margin-left: 15px\">\n\nUsing the name xxx-foreground isn't very intuitive when talking about daemon processes. Yes, the command, when issued in a\nterminal, will start the daemon in foreground so the names of the commands does have some logic to them, but then again, starting\nin the foreground is the default behavior of any command. And when the same command is started from the CLI, it will be started in\nthe background, despite its name.\n\nThe daemons are therefore now renamed:\n\n- connector-foreground =&gt; userd\n- daemon-foreground =&gt; rootd\n- kubeauth-foreground =&gt; kubeauthd\n\nThis also affects the Telepresence hidden CLI commands `telepresence daemon-foreground` and `telepresence connector-foreground`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add retry logic for tunnel connection attempts</div></div>\n<div style=\"margin-left: 15px\">\n\nThe tunnel dialer now uses a backoff-based retry mechanism when establishing connections. This ensures that dialing attempts for both TCP and UDP protocols persist if the target IP is not immediately ready to receive requests.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Retry mechanism for client tunnel creation</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic agent now includes a backoff-based retry mechanism when establishing tunnel streams to a client. This prevents \"no dial watcher\" connection failures caused by a race condition where the tunnel request arrives before the client has fully initialized its communication channel.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix \"close of closed channel\" panic in the root daemon process.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe root daemon process would sometimes panic with \"close of closed channel\" due to a race condition in the DNS cache logic. This issue has been fixed.\n</div>\n\n## Version 2.25.2 <span style=\"font-size: 16px;\">(December 26)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Ensure that the exit code from a docker command becomes the exit code of the Telepresence command.</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen running a Docker command using `telepresence docker-run` or `telepresence curl`, the exit code would be 1 for all non-zero exit codes from the Docker command. This has been fixed so that the exit code from the Docker command becomes the exit code of the Telepresence command.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix a bug causing truncation of command text when generating external command help.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Telepresence CLI would truncate the command text when generating help for external commands such as `docker compose` that had text spanning more than one line. This has been fixed so that the full command text is displayed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix schema for agent.image.pullSecrets</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `agent.image.pullSecrets` is referenced by the helm chart's deployment.yaml but was previously disallowed by the schema file.\n</div>\n\n## Version 2.25.1 <span style=\"font-size: 16px;\">(November 10)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Volumes did not mount correctly when using `telepresence connect --docker` when Docker had IPv6 enabled.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence failed to mount volumes after connecting with `telepresence connect --docker` when Docker Engine had IPv6 enabled in its default bridge network. Disabling IPv6 in the Telepresence client configuration did not resolve the issue. This was fixed in Telepresence Volume Plugin \"telemount\" version 0.3.2, which circumvented a [bug in sshfs](https://github.com/libfuse/sshfs/issues/335). Additionally, the volume plugin will no longer use IPv6 when the client configuration `docker.enableIPv6` is set to `false`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Remove unnecessary setcap from traffic binary</div></div>\n<div style=\"margin-left: 15px\">\n\nThe setcap capability (cap_net_bind_service) was removed from the traffic binary build process. This capability was originally added to allow the binary to bind to privileged ports, specifically port 443 for the mutating webhook. Since version 2.24.0, the default mutating webhook port was changed to 8443 (a non-privileged port), making this capability unnecessary. Removing it simplifies the build process and reduces the security surface area.\n</div>\n\n## Version 2.25.0 <span style=\"font-size: 16px;\">(October 16)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[HTTP Intercepts with HTTP header and path filtering](howtos/engage#intercept-your-application)</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence now supports HTTP Intercepts, enabling fine-grained HTTP traffic filtering for intercepts.\nUsers can intercept only specific HTTP requests based on headers and URL paths using the new `--http-header`,\n`--http-path-prefix`, `--http-path-equal`, and `--http-path-regex` flags. This allows multiple developers\nto work on the same service simultaneously by intercepting only their specific traffic patterns, rather than\nintercepting all traffic to a service.\n\n**Routing Precedence Model**: Header-based intercepts take priority over path-only intercepts. When multiple\nintercepts are active on the same workload, requests are evaluated against header-based filters first, then\npath-only filters. This enables different developers to use header-based personal intercepts (e.g.,\n`x-user=alice`) while others use path-based intercepts (e.g., `/admin/*`) without conflicts.\n\n**Conflict Detection**: Intercepts conflict only when their filters would route the same traffic to different\ndestinations. Key rules:\n\n- Different header values (e.g., `X-User=adam` vs `X-User=bertil`) do NOT conflict\n- Header filters use subset logic: `X-User=adam` conflicts with `X-User=adam, X-Session=123` (first is subset)\n- Same headers with different paths do NOT conflict: `X-User=adam + /api/*` vs `X-User=adam + /admin/*`\n- Path-only intercepts operate at a lower priority tier than header-based intercepts\n\nThe feature maintains full backward compatibility with existing TCP intercepts.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[TLS/mTLS Intercept Support](howtos/mtls)</div></div>\n<div style=\"margin-left: 15px\">\n\nSupport was added for HTTP-filtered intercepts on applications using TLS/mTLS encryption. The new functionality enables\nTelepresence to decrypt and inspect encrypted traffic by accessing TLS certificates, facilitating debugging and testing of secure\napplications.\n\nCertificates can be accessed via mounted volumes or Kubernetes secrets in the same namespace. New annotations\n(`telepresence.io/downstream-cert-path` and `telepresence.io/downstream-cert-secret`) allow configuration of certificate paths or\nsecrets for decrypting traffic on specified ports. For mTLS, the `telepresence.io/upstream-cert-` annotation prefix supports\nre-encryption of upstream traffic using client-side certificates.\n\nFor self-signed certificates, the `telepresence.io/upstream-insecure-skip-verify` annotation bypasses verification, enabling\nHTTP-filtered intercepts in development environments. The `--plaintext` option allows unencrypted traffic during intercepts or\nwiretaps.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add MCP server to Telepresence CLI</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Telepresence CLI now includes a lightweight MCP server that can be used to allow local AI agents to execute some CLI commands, such as connecting to a traffic manager, listing interceptable apps, and creating an intercept. The server can be enabled using the new `telepresence mcp claude enable` or `telepresence mcp vscode enable` commands.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Enhance Resilience of Engagements During Traffic-Manager Redeploys</div></div>\n<div style=\"margin-left: 15px\">\n\nThe telepresence client and traffic-agent now automatically reconnect to the traffic-manager after a restart. Upon reconnection, they share their current state, ensuring ongoing engagements remain uninterrupted. This improvement minimizes user impact during traffic-manager upgrades.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add support for IPv6 and dual-stack when using `telepresence connect --docker`</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence now supports using `telepresence connect --docker` together with Kubernetes single-stack IPv4\nnetworking, single-stack IPv6 networking, or dual-stack networking with both network families. Both are\nenabled by default, but can be disabled by setting the `client.docker.enableIPv4` or `client.docker.enableIPv6`\nto false in the Helm chart, or by using the corresponding settings `docker.enableIPv4` or `docker.enableIPv6`\nthe client configuration file.\n\nThe new dual-stack support requires the teleroute network plugin 0.4.0 or later. The client will install this\nversion automatically unless you work in an air-gapped environment.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[RESTful API Service Reintroduced with HTTP Filtering Support](reference/restapi)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Telepresence RESTful API service has been restored with enhanced support for HTTP header and path filtering. This service enables workloads to programmatically query whether they should handle requests based on active intercepts. Added `--metadata` flag allows attaching custom metadata to intercepts that can be retrieved through the API endpoints. The API server is now accessible via `TELEPRESENCE_API_HOST` and `TELEPRESENCE_API_PORT` environment variables in both cluster pods and local intercept handlers.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">More efficient DNS handling in the traffic-manager</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence will no longer send DNS queries for A and AAAA records to the traffic-manager. Instead, it will\nsend a single query for the name and then derive the record type from the type of IP address (IPv4 or IPv6) in\nthe response. This reduces the number of DNS queries sent to the cluster's DNS server and makes the behavior\nmore consistent with the `net.LookupNetIP` function in the Go standard library, which the traffic-manager\nultimately uses.\n\nThe lookups will now be performed exclusively by the traffic-manager, never by the traffic-agent. This means\nthat traffic-agents with special DNS configurations might stop working. If this is a problem, the old behavior\ncan be restored by setting the `client.dns.useComplexLookup` parameter in the Helm chart or the\n`dns.useComplexLookup` parameter in the client configuration file.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Updated Helm chart to include keywords and the source repository URL.</div></div>\n<div style=\"margin-left: 15px\">\n\nImproves the Helm chart's discoverability on platforms like Artifact Hub and automatically adds a direct link to the source code for users, providing better context.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Telepresence client now requires a traffic manager version of at least 2.21.0.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic manager is now required to be at least version 2.21.0. Versions earlier than 2.21.0 will no longer work. The reason for this is that implementing the new reconnect behavior would require too much conditional code with older traffic-managers, and a lot of functionality wouldn't work anyway.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Build binaries and docker images that are stripped from dwarf and debug info.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Telepresence binaries and docker images are now built with the `-w -s` flags, which strip the debug symbols and the DWARF information. This reduces the size of the binaries and docker images by about 50MB. Debug binaries can be built using `DEBUG=1 make build`.\n</div>\n\n## Version 2.24.1 <span style=\"font-size: 16px;\">(September  5)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix invalid filename generated by telepresence gather-logs command](https://github.com/telepresenceio/telepresence/issues/3956)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `telepresence gather-logs` command would generate a filename that was invalid on Windows unless the user specified the filename explicitly using the `--output-file` flag. This was fixed using a more condensed format for the timestamp in the filename.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">A `telepresence connect --docker` fails kubeconfig points to a port-forwarded localhost</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence would fail to connect to a cluster from within a container if the kubeconfig pointed to a port-forwarded localhost because that localhost is not reachable from within the container. This situation is now detected so that the address used from within the container has \"localhost\" replaced with \"host.docker.internal\", or an alternative alias configured by the user using the new `docker.hostGateway` parameter in the client configuration.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">A `telepresence connect --docker` would fail with some k3s configurations</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence would fail to connect to a k3s cluster unless the cluster's IP was a loopback address. This is now changed so that any IP:port combination is accepted as long as a container can be found that defines a mapping for it.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Restore default value for agent-state.yaml in the traffic-manager configmap](https://github.com/telepresenceio/telepresence/pull/3953)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe value was previously an empty string which caused problems when when Argo CD tried to synchronize it.\n</div>\n\n## Version 2.24.0 <span style=\"font-size: 16px;\">(August 25)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Support for Docker Compose](howtos/docker-compose)</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence now supports integration with Docker Compose. It connects to and interacts with cluster resources by utilizing `x-tele` extensions within a Docker Compose specification. These extensions configure your local services to effectively act as handlers for Telepresence connections, providing them with the necessary access to the traffic, volumes, and environment of the engaged container.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Serve up a web-page with telepresence serve.](reference/cli/telepresence_serve)</div></div>\n<div style=\"margin-left: 15px\">\n\nA new `telepresence serve <service>` command was added that starts a web browser on the specified service. The command is especially useful when used in combination with `telepresence connect --docker` because it will then expose the given service on a random port on localhost.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to optionally clean up sidecars that have been idle above a specified duration</div></div>\n<div style=\"margin-left: 15px\">\n\nAdded configuration parameter `agent.maxIdleTime` to the Helm Chart, to control how long a sidecar can be idle before it is cleaned up. The updating of latestEngagementTime is done every 1m in the Remain Call, which is now called every 1min, compared to every 5s in the past. The agent state (containing latestEngagementTime) is updated in memory, and lazily persisted to the traffic-manager configmap every 2min. Removal of the sidecar is also done in the Remain Call, if the sidecar has been idle for longer than the configured `agent.maxIdleTime`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add option to drop client label in prometheus metrics for GDPR compliance](https://github.com/telepresenceio/telepresence/issues/3491)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Helm Chart now has a `prometheus.dropClientLabel` option that can be set to true to drop the client label from the prometheus metrics. This is useful for GDPR compliance, as the client label contains personal data, which can be potentially problematic, i.e allowing the ability to track the working times of an individual.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Prefix metrics with \"telepresence_\"](https://github.com/telepresenceio/telepresence/issues/3920)</div></div>\n<div style=\"margin-left: 15px\">\n\nAvoids metric conflicts and makes these more explicit to improve search in observability stacks.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[CLI documentation in markdown format](reference/cli/telepresence)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Telepresence CLI is now capable of generating its own documentation in markdown format using the new `telepresence man-pages` command. The generated documentation is included under the heading \"Telepresence CLI\" in the the Telepresence reference documentation.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Service Port Rerouting</div></div>\n<div style=\"margin-left: 15px\">\n\nThe telepresence connect command introduces a new `--reroute-remote <host>:<port>:<new-port>[/{tcp|udp}]` flag, allowing users to remap service ports. This feature redirects requests sent to `<host>:<new-port>` to `<host>:<port>` within the Telepresence VIF. The flag can be repeated.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Local Port Rerouting</div></div>\n<div style=\"margin-left: 15px\">\n\nThe telepresence connect command introduces a new `--reroute-local <local-port>:<host>:<port>[/{tcp|udp}]` flag, allowing users to redirect requests sent to ports on localhost to arbitrary service ports. This feature enables requests sent to `localhost:<local-port>` to be redirected to `<host:port>`. The flag can be repeated.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add information about using Kubernetes auth plugins when using Telepresence CLI in a container](reference/inside-container#kubernetes-auth-plugins)</div></div>\n<div style=\"margin-left: 15px\">\n\nKubernetes, and hence the Telepresence CLI, must have access to auth plugins declared in the kubeconfig. A section was added to the documentation explaining how to achieve this when using the Telepresence CLI in a container.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add log directory to the output of `telepresence config view`</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `telepresence config view` command now includes the path to the directory where the Telepresence logs are stored.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The default port for the mutating webhook is now 8443. It used to be 443</div></div>\n<div style=\"margin-left: 15px\">\n\nPort numbers below 1000 are reserved for privileged processes and are often restricted by firewalls. Consequently, the default port for the mutating webhook was changed from 443 to 8443. You can override this default port using the agentInjector.webhook.port value in the Helm Chart. This change is particularly significant for clusters using Telepresence, where firewall rules limit the admission webhook's access to worker nodes, such as in an Amazon EKS cluster.\n</div>\n\n## Version 2.23.6 <span style=\"font-size: 16px;\">(July 23)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Public DNS names aren't resolved from local docker application started by Telepresence</div></div>\n<div style=\"margin-left: 15px\">\n\nA container running using `telepresence docker-run` or `telepresence <engage-type> --docker-run` was not able to resolve public DNS names such as \"google.com\". The problem is caused by an undocumented behavior in Docker's internal DNS-resolver when the `--dns` flag is used in conjunction with the teleroute network, where the DNS-server no longer finds public names even thought another bridge network is connected. The problem was solved by using a fallback resolver in the Telepresence DNS resolver.\n</div>\n\n## Version 2.23.5 <span style=\"font-size: 16px;\">(July 20)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Let docker.Start pass on --interactive to docker start.</div></div>\n<div style=\"margin-left: 15px\">\n\nAn `-i` or `--interactive` flag given when the user runs a container with telepresence must be propagated to `docker start` to attach `stdin`.\n</div>\n\n## Version 2.23.4 <span style=\"font-size: 16px;\">(July 18)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Never truncate meaningful output from a command</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new human-friendly output using a progress reporter would sometimes truncate error output. This is no longer the case. Instead, all output will be wrapped.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Typo in client mount-policy \"RemoteReadonly\" should be \"RemoteReadOnly\"</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Helm Chart correctly expects the remote read-only mount policy to be `RemoteReadOnly`, but the client expected it to be `RemoteReadonly` (without a leading capital letter in the word \"only\").\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[DNS server does not respect semicolons as comments in resolv.conf files](https://github.com/telepresenceio/telepresence/issues/3908)</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence does not work correctly if `/etc/resolv.conf` contains semicolons, which are valid comments as of [linux manpage](https://man7.org/linux/man-pages/man5/resolv.conf.5.html).\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Use NXDOMAIN instead SERVFAIL for DNS recursion errors (timeouts)](reference/config#recursioncheck)</div></div>\n<div style=\"margin-left: 15px\">\n\nA DNS for a single label name that fails in a minikube - or another type of local cluster - will sometimes result in a recursive lookup on the host. Without any type of recursion detection, this lookup will timeout waiting for itself. Previously, this resulted in a `SERVFAIL` from the cluster DNS, which triggered renewed lookup attempts that never stopped. This is now changed so that the same type of timeouts instead results in an `NXDOMAIN` error that doesn't trigger renewed attempts.\nAlso, the recursion check now handles that the cluster's DNS adds suffixes from its search-path.\n</div>\n\n## Version 2.23.3 <span style=\"font-size: 16px;\">(July  7)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix tunnel channel reuse in traffic-agent to prevent connection failures</div></div>\n<div style=\"margin-left: 15px\">\n\nPreviously, when a tunnel became active, the map maintained by the traffic-agent containing channel values for agent-to-client tunnels wasn't cleared. This resulted in closed channels remaining in the map. When port numbers were eventually reused, these stale entries would be discovered and their closed channels would cause immediate stream termination, leading to data loss.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The -p flags would have no effect in combination with --docker-run</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen using `telepresence <engage> --docker-run` with a `-p <port spec>` flag, the Docker driver silently\nignored the port specification. This occurred because the `--network=<teleroute network>` flag disabled both\nadditional network directives and the default bridge network (which is normally used when no network is\nspecified). This has been resolved by:\n\n1. Adding the teleroute network after container creation instead of using a flag\n2. Replacing the single `docker run` command with a sequence of:\n   - `docker create`\n   - Network addition\n   - `docker start`\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Requests lost when using wiretap</div></div>\n<div style=\"margin-left: 15px\">\n\nWiretap connection `Close` and `Write` could sometimes be out of sync, so that the `Close` would be executed before the `Write`, causing a \"read/write on closed pipe\" error and loss of data.\n</div>\n\n## Version 2.23.2 <span style=\"font-size: 16px;\">(June 27)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Adding an alsoProxy subnet with 32-bit mask no longer works on macOS</div></div>\n<div style=\"margin-left: 15px\">\n\nRouting improvements introduced in 2.23.0 surfaced a problem when using special handling of submets with 32-bit masks. The special handling now causes problems on macOS. The logic is now conditioned to only run under linux since it's no longer needed on other operating systems.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The gather-logs command produces no cluster-side logs when connected with --docker</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `telepresence gather-logs` command did not include cluster-side logs when connected using `--docker`, because it tried to store such logs in a temporary directory created by the CLI. The directory was not mounted in the daemon container. This is now changed so that the temporary directory is created under the users cache directory, which is guaranteed to be mounted on the container.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Docker volume mounts failing when connected using both --docker --proxy-via flags</div></div>\n<div style=\"margin-left: 15px\">\n\nThe volume mounter would fail when doing a `telepresence connect --docker --proxy-via all=<workload>` followed by an intercept using `--docker-run`, because the bridge that the daemon created would try to access the intercepted pod using its proxied IP. Now, the bridge will instead use the pod's real IP.\n</div>\n\n## Version 2.23.1 <span style=\"font-size: 16px;\">(June 24)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">New telepresence helm version command.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new `telepresence helm version` command prints the version of the helm client that is embedded in the telepresence binary.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Engagement disconnects after certain amount of time](https://github.com/telepresenceio/telepresence/issues/3861)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe configuration parameter `connectionTTL`, controlling how long a client could be completely idle before\nthe traffic-manager or traffic-agent would consider it dead and disconnect (default 24 hours), had no effect.\nInstead, an engagement would disconnect after 2 hours (the default gRPC `keepAlive.Time` duration). The\ndefault of 24 hours is now reinstated.\n\nThe Helm value `client.connectionTTL` was moved to `grpc.connectionTTL` because it is a server configuration.\nThe old value will still work, but it is deprecated and will be removed eventually.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Telepresence breaks if config.yml exists but is empty](https://github.com/telepresenceio/telepresence/issues/3887)</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence would refuse to connect with a misleading error if the `config.yml` file containing the client's configuration parameters existed but was empty.\n</div>\n\n## Version 2.23.0 <span style=\"font-size: 16px;\">(June 17)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">New telepresence wiretap command</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new `telepresence wiretap` command introduces a read-only form of an `intercept` where the original container will run unaffected while a copy of the wiretapped traffic is sent to the client.\nSimilar to an `ingest`, a `wiretap` will always enforce read-only status on all volume mounts, and since that makes the `wiretap` completely read-only, there's no limit to how many simultaneous wiretaps that can be served. In fact, a `wiretap` and an `intercept` on the same port can run simultaneously.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add Telepresence Docker Network Plugin \"Teleroute\"](reference/teleroute)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new Teleroute plugin makes it possible for containers to use the Telepresence daemon's VIF without having\nto change their network mode, i.e. a `--network container:<daemon container>` is no longer needed. Instead,\na container can use a custom network created when the Telepresence daemon connects to the cluster.\nThis network uses the new driver \"teleroute\" which is provided by Telepresence.\n\nWith the Teleroute Docker network plugin in place, there's no longer a need for special handling of network\nrelated docker flags, and the following changes have been made:\n\n1. The Teleroute Docker network driver will be installed unless it is already present.\n2. A Teleroute network will be created when starting the Telepresence daemon as a container. This network will\n   then communicate with that container and expose the same CIDRs as the daemon's VIF.\n3. A container started with `telepresence curl`, or\n   `telepresence {ingest|intercept|replace|wiretap} --docker-{run|build|debug}` will no longer change its\n   network mode using `--network container:<daemon container>`, instead it will use\n   `--network <name of teleroute network>`.\n4. As a consequence of #3, published ports and other networks that are added no longer need special handling\n   using socat containers, so all of that has been removed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Control whether the initContainer injection is enabled/disabled</div></div>\n<div style=\"margin-left: 15px\">\n\nThe initContainer injection can be optionally disabled by setting the `agent.initContainer.enabled` parameter to false in the `values.yaml` file of the Helm chart. This feature was added to improve compatibility with systems like OpenShift where the initContainer injection cannot be used due to inability to give initContainer NET_ADMIN permissions.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Human friendly progress reporting</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence now uses a progress reporter that is very similar to the one used by Docker compose. The implementation is a variation of that reporter's source code, so big thanks to the Docker compose CLI authors for making it available as OSS.\nA new global `--progress <progress>` flag was added. It defaults to \"auto\" which means that the style is chosen depending on whether the command runs from a tty type terminal. Other possible values are \"plain\", \"quiet\", and \"json\". `--progress quiet` is implied when formatted output is chosen using `--output json|yaml`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add the ability to use a name for the target host, and defer its resolution</div></div>\n<div style=\"margin-left: 15px\">\n\nKnowing the IP of the local service that acts as the handler service for an intercept, replace, or wiretap is not possible until that service has been started, and telepresence will therefore now accept a name for the `--address` flag. The name is not resolved by the daemon until a request is made to the engaged container on a port that is routed to the local service.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add intercept.mountsRoot to the client configuration</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new `intercept.mountsRoot` can be set to a directory that will be used as the root for all automatically generated mount directories. The default is to use the platforms temp directory.\nThe setting is not used on windows, where the mounts use drive letters.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add docker.addHostGateway to the client configuration.</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen `docker.addHostGateway` is set to `true`, the `docker run` that starts the containerized Telepresence daemon will include the flag `--add-host host.docker.internal:host-gateway`.\nThe flag is set to `true` by default on linux platforms and `false` on other platforms.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Client configuration to override the Helm download URL</div></div>\n<div style=\"margin-left: 15px\">\n\nThe default download URL `oci://ghcr.io/telepresenceio/telepresence-oss` used when installing Helm charts with versions that differ from the version of the embedded Helm chart can now be overridden using the client config value `helm.chartURL`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Dropped support for Telepresence legacy flags</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `telepresence` CLI command will no longer support legacy flags such as:\n\n- `--swap-deployment`\n- `--new-deployment`\n- `--docker-mount`\n- `--method`\n\nA \"Legacy Telepresence command used\" warning has been printed for several years now, and the mapping for the\n`--swap-deployment` was the `intercept` command, which is very confusing today since we now have the `replace`\ncommand.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Let containerized daemon consistently use the same port for gRPC</div></div>\n<div style=\"margin-left: 15px\">\n\nThe port used for the containerized gRPC was randomly selected using the hosts network namespace. This is now changed so that the port used by the container is preset and configurable and then mapped to a random port on the host.\nThe port number can be configured using `grpc.daemonPort` and defaults to `4038`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Telepresence fails to start the root daemon on Windows unless current user is the administrator](https://github.com/telepresenceio/telepresence/issues/3875)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe telepresence CLI starts a user daemon and a root daemon. The latter is started using administrator privileges. On a Windows box, this means that the root daemon runs using a different user account (typically \"Administrator\") unless the current user can run processes with elevated privileges. The socket used for communication with the root daemon was assumed to reside in `%USERPROFILE%\\AppData\\Local\\telepresence` and was therefore not found by the CLI and the user daemon. The location will henceforth always be based on the `%USERDATA` of the CLI user.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Telepresence DNS Fallback stripping CNAME information from DNS Records.](https://github.com/telepresenceio/telepresence/issues/3873)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe fallback DNS server used on Linux systems without a systemd.resolved configuration, would assume that suffixes belonging to the `search` defined in the `/etc/resolved` had been added by the caller. Since this search path was assumed to be intended for the local machine only, the suffix was stripped off prior to sending the name to the cluster for resolution. This made queries fail that relied on the qualified name to resolve CNAME records. The logic stripping the suffix was therefore removed.\n</div>\n\n## Version 2.22.6 <span style=\"font-size: 16px;\">(June  3)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Regression causing \"unexpected slice size\" with older traffic-managers.</div></div>\n<div style=\"margin-left: 15px\">\n\nOlder traffic-managers have a different way of reporting the service-subnet. The new way, using a list of subnets reused a proto slice in the GRPC message that was expected to be empty, but older traffic-managers will pass the IP of the kube-dns here. It cannot be parsed as a list of subnets. A check that remedies this mismatch was inserted.\n</div>\n\n## Version 2.22.5 <span style=\"font-size: 16px;\">(May 29)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Unable to correctly determine service CIDR with Kubernetes >= 1.33</div></div>\n<div style=\"margin-left: 15px\">\n\nStarting with Kubernetes 1.33, the strategy of extracting the cluster's service CIDR from an error message no longer works because the error message has changed. The root cause for this is that Kubernetes introduced the ability to use [Multiple Service CIDRs](https://gist.github.com/aojea/c20eb117bf1c1214f8bba26c495be9c7). Since `ServiceCIDR` is now a resource, it can be easily retrieved (and modified) using standard Kubernetes client API calls, and this is what the traffic manager will use going forward.\nThe fix required an addition to the traffic-manager's RBAC, granting it sufficient permissions to list `networking.k8s.io/servicecidrs`. A future enhancement will allow the traffic manager to watch for service CIDR changes.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Helm chart schema type for nodeSelector was incorrect</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Helm chart schema for the `nodeSelector` value was incorrect. Kubernetes defines different types for nodeSelector (inside PodSpec objects) and NodeSelector (inside NodeAffinity, VolumeNodeAffinity and a bunch of other places). The schema was changed to use the correct type. The name `nodeSelector` is still used in the Helm chart so this change is backwards compatible.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Pods with container ports named the same caused intercept to fail</div></div>\n<div style=\"margin-left: 15px\">\n\nIntercept container ports now have numbers appended to them if there are multiple ports from multiple containers with the same name. This bugfix works around an issue where Kubernetes allows multiple port definitions in a pod spec to have the same name.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Don't include k8s-defs.json to chart package</div></div>\n<div style=\"margin-left: 15px\">\n\nThe k8s-defs.json was unnecessarily included to the Helm chart package and this increased the Helm release secret size so much that it could prevent installation of the Helm chart depending on k8s settings. To fix this k8s-defs.json is not included to the Helm chart anymore.\n</div>\n\n## Version 2.22.4 <span style=\"font-size: 16px;\">(April 26)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Don't require internet access when installing the traffic-manager using Helm</div></div>\n<div style=\"margin-left: 15px\">\n\nA regression occurred with the introduction of Helm validation in version 2.22.0. The schema relied on external HTTPS links, which inadvertently created a requirement for internet accessibility. To resolve this, we have embedded these resources within the schema, thus removing the need for an internet connection.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Client failed connect with \"failed to exit idle mode\" in the connector.log after being idle</div></div>\n<div style=\"margin-left: 15px\">\n\nThe port-forward connections used for connecting the daemon to the traffic-agents were using an incorrect context, causing them to fail after being idle for some time.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix deadlock in Telepresence daemon</div></div>\n<div style=\"margin-left: 15px\">\n\nA deadlock would sometimes occur in the Telepresence daemon that prevented it from doing a clean exit during `telepresence quit`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Don't log error message when a pod watcher ends due to cancellation</div></div>\n<div style=\"margin-left: 15px\">\n\nErrors printed in the daemon log during normal cancellation of the WatchAgentPods goroutine are now removed.\n</div>\n\n## Version 2.22.3 <span style=\"font-size: 16px;\">(April  8)</span>\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The Windows install script will now install Telepresence to \"%ProgramFiles%\\telepresence\"</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence is now installed into \"%ProgramFiles%\\telepresence\" instead of \"C:\\telepresence\".\nThe directory and the Path entry for  `C:\\telepresence` are not longer used and should be removed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[The Windows install script didn't handle upgrades properly](https://github.com/telepresenceio/telepresence/issues/3827)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe following changes were made:\n\n  - The script now requires administrator privileges\n  - The Path environment is only updated when there's a need for it\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[The Telepresence Helm chart could not be used as a dependency in another chart.](https://github.com/telepresenceio/telepresence/issues/3833)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe JSON schema validation implemented in Telepresence 2.22.0 had a defect: it rejected the `global` object. This object, a Helm-managed construct, facilitates the propagation of arbitrary configurations from a parent chart to its dependencies. Consequently, charts intended for dependency use must permit the presence of the `global` object.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Recreating namespaces was not possible when using a dynamically namespaced Traffic Manager](https://github.com/telepresenceio/telepresence/issues/3831)</div></div>\n<div style=\"margin-left: 15px\">\n\nA shared informer was sometimes reused when namespaces were removed and then later added again, leading to errors like \"handler ... was not added to shared informer because it has stopped already\".\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Single label name DNS lookups didn't work unless at least one traffic-agent was installed</div></div>\n<div style=\"margin-left: 15px\">\n\nA problem with incorrect handling of single label names in the traffic-manager's DNS resolver was fixed. The problem would cause lookups like `curl echo` to fail, even though telepresence was connected to a namespace containing an \"echo\" service, unless at least one of the workloads in the connected namespace had a traffic-agent.\n</div>\n\n## Version 2.22.2 <span style=\"font-size: 16px;\">(March 28)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Panic when using telepresence replace in a IPv6-only cluster](https://github.com/telepresenceio/telepresence/issues/3828)</div></div>\n<div style=\"margin-left: 15px\">\n\nA \"slice bounds out of range\" would occur when the targeted Pod's Traffic Agent requested a local dialer to\nbe created on the client. This was due to a glitch in the VPN-tunnel implementation that got triggered when\na remote IPv6-address was combined with a local IPv4-address.\n</div>\n\n## Version 2.22.1 <span style=\"font-size: 16px;\">(March 27)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Only restore inactive traffic-agent after a replace.</div></div>\n<div style=\"margin-left: 15px\">\n\nA regression in the 2.20.0 release would cause the traffic-agent to be replaced with a dormant version that\ndidn't touch any ports when an intercept ended. This terminated other ongoing intercepts on the same pod.\nThis is now changed so that the traffic-agent remains unaffected for this use-case.\n</div>\n\n## Version 2.22.0 <span style=\"font-size: 16px;\">(March 14)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">New telepresence replace command.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new `telepresence replace` command simplifies and clarifies container replacement.\n\nPreviously, the `--replace` flag within the `telepresence intercept` command was used to replace containers.\nHowever, this approach introduced inconsistencies and limitations:\n\n* **Confusion:** Using a flag to modify the core function of a command designed for traffic interception led\n  to ambiguity.\n* **Inaccurate Behavior:** Replacement was not possible when no incoming traffic was intercepted, as the\n  command's design focused on traffic routing.\n\nTo address these issues, the `--replace` flag within `telepresence intercept` has been deprecated. The new\n`telepresence replace` command provides a dedicated and consistent method for replacing containers, enhancing\nclarity and reliability.\n\nKey differences between `replace` and `intercept`:\n\n1. **Scope:** The `replace` command targets and affects an entire container, impacting all its traffic, while\n   an `intercept` targets specific services and/or service/container ports.\n2. **Port Declarations:** Remote ports specified using the `--port` flag are container ports.\n3. **No Default Port:** A `replace` can occur without intercepting any ports.\n4. **Container State:** During a `replace`, the original container is no longer active within the cluster.\n\nThe deprecated `--replace` flag still works, but is hidden from the `telepresence intercept` command help, and\nwill print a deprecation warning when used.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add json-schema for the Telepresence Helm Chart</div></div>\n<div style=\"margin-left: 15px\">\n\nHelm can validate a chart using a json-schema using the command `helm lint`, and this schema can be part of the actual Helm chart. The telepresence-oss Helm chart now includes such a schema, and a new `telepresence helm lint` command was added so that linting can be performed using the embedded chart.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">No dormant container present during replace.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence will no longer inject a dormant container during a `telepresence replace` operation. Instead, the\nTraffic Agent now directly serves as the replacement container, eliminating the need to forward traffic to the\noriginal application container. This simplification offers several advantages when using the `--replace` flag:\n\n  - **Removal of the init-container:** The need for a separate init-container is no longer necessary.\n  - **Elimination of port renames:** Port renames within the intercepted pod are no longer required.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">One single invocation of the Telepresence intercept command can now intercept multiple ports.</div></div>\n<div style=\"margin-left: 15px\">\n\nIt is now possible to intercept multiple ports with one single invocation of `telepresence intercept` by just repeating the `--port` flag.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Unify how Traffic Manager selects namespaces](install/manager#static-versus-dynamic-namespace-selection)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe definition of what namespaces that a Traffic Manager would manage use was scattered into several Helm\nchart values, such as `manager.Rbac.namespaces`, `client.Rbac.namespaces`, and\n`agentInjector.webhook.namespaceSelector`. The definition is now unified to the mutual exclusive top-level\nHelm chart values `namespaces` and `namespaceSelector`.\n\nThe `namespaces` value is just for convenience and a short form of expressing:\n```yaml\nnamespaceSelector:\n  matchExpressions:\n   - key: kubernetes.io/metadata.name\n     operator: in\n     values: <namespaces>.\n```\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Improved control over how remote volumes are mounted using mount policies</div></div>\n<div style=\"margin-left: 15px\">\n\nMount policies, that affects how the telepresence traffic-agent shares the pod's volumes, and also how the client will mount them, can now be provided using the Helm chart value `agent.mountPolicies` or as JSON object in the workload annotation `telepresence.io/mount-policies`. A mount policy is applied to a volume or to all paths matching a path-prefix (distinguished by checking if first character is a '/'), and can be one of `Ignore`, `Local`, `Remote`, or `RemoteReadOnly`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">List output includes workload kind.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe output of the `telepresence list` command will now include the workload kind (deployment, replicaset, statefulset, or rollout) in all entries.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to override the default securityContext for the Telepresence init-container</div></div>\n<div style=\"margin-left: 15px\">\n\nUsers can now use the Helm value `agent.initSecurityContext` to override the default securityContext for the Telepresence init-container.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Let download page use direct links to GitHub</div></div>\n<div style=\"margin-left: 15px\">\n\nThe download links on the release page now points directly to the assets on the download page, instead of using being routed from getambassador.io/download/tel2oss/releases.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Use telepresence.io as annotation prefix instead of telepresence.getambassador.io</div></div>\n<div style=\"margin-left: 15px\">\n\nThe workload and pod annotations used by Telepresence will now use the prefix `telepresence.io` instead of `telepresence.getambassador.io`. The new prefix is consistent with the prefix used by labels, and it also matches the host name of the documentation site. Annotations using the old name will still work, but warnings will be logged when they are encountered.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Make the DNS recursion check configurable and turn it off by default.</div></div>\n<div style=\"margin-left: 15px\">\n\nVery few systems experience a DNS recursion lookup problem. It can only occur when the cluster runs locally and the cluster's DNS is configured to somehow use DNS server that is started by Telepresence. The check is therefore now configurable through the client setting `dns.recursionCheck`, and it is `false` by default.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Trigger the mutating webhook with Kubernetes eviction objects instead of patching workloads.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence will now attempt to evict pods in order to trigger the traffic-agent's injection or removal, and revert to patching workloads if evictions are prevented by the pod's disruption budget. This causes a slight change in the traffic-manager RBAC, as the traffic-manager must be able to create \"pod/eviction\" objects.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The telepresence-agents configmap is no longer used.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic-agent configuration was moved into a pod-annotation. This avoids sync problems between the telepresence-agents (which is no no longer present) and the pods.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Drop deprecated current-cluster-id command.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe clusterID was deprecated some time ago, and replaced by the ID of the namespace where the traffic-manager is installed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Make telepresence connect --docker work with Rancher Desktop</div></div>\n<div style=\"margin-left: 15px\">\n\nRancher Desktop will start a K3s control-plane and typically expose the Kubernetes API server at `127.0.0.1:6443`. Telepresence can connect to this cluster when running on the host, but the address is not available when connecting in docker mode.\nThe problem is solved by ensuring that the Kubernetes API server address used when doing a `telepresence connect --docker` is swapped from 127.0.0.1 to the internal address of the control-plane node. This works because that address is available to other docker containers, and the Kubernetes API server is configured with a certificate that accepts it.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Rename charts/telepresence to charts/telepresence-oss.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Helm chart name \"telepresence-oss\" was inconsistent with its contained folder \"telepresence\". As a result, attempts to install the chart using an argo ApplicationSet failed. The contained folder was renamed to match the chart name.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Conflict detection between namespaced and cluster-wide install.](install/manager#namespace-collision-detection)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe namespace conflict detection mechanism would only discover conflicts between two _namespaced_ Traffic Managers trying to manage the same namespace. This is now fixed so that all types conflicts are discovered.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Don't dispatch DNS discovery queries to the cluster.</div></div>\n<div style=\"margin-left: 15px\">\n\nmacOS based systems will often PTR queries using nameslike `b._dns-sd._udp`, lb._dns-sd._udp`, or `db-dns-sd._udp`. Those queries are no longer dispatched to the cluster.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Using the --namespace option with telepresence causes a deadlock.</div></div>\n<div style=\"margin-left: 15px\">\n\nUsing `telepresence list --namespace <ns>` with a namespace different from the one that telepresence was connected to, would cause a deadlock, and then produce an empty list.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix problem with exclude-suffix being hidden by DNS search path.</div></div>\n<div style=\"margin-left: 15px\">\n\nIn some situations, a name ending with an exclude-suffix like \"xyz.com\" would be expanded by a search path into \"xyz.com.&lt;connected namespace&gt;\" and therefore not be excluded. Instead, the name was sent to the cluster to be resolved, causing an unnecessary load on its DNS server.\n</div>\n\n## Version 2.21.3 <span style=\"font-size: 16px;\">(February  6)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Using the --proxy-via flag would sometimes cause connection timeouts.</div></div>\n<div style=\"margin-left: 15px\">\n\nTypically, a `telepresence connect --proxy-via <subnet>=<workflow>` would fail with a \"deadline exceeded\" message when several workloads were present in the namespace, the one targeted by the proxy-via didn't yet have an agent installed, and other workloads had an agent. This was due to a race condition in the logic for the agent-based port-forwards in the root daemon. The conditions causing this race are now eliminated.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix panic in root daemon when using the \"allow conflicting subnets\" feature on macOS.</div></div>\n<div style=\"margin-left: 15px\">\n\nA regression was introduced in version 2.21.0, causing a panic due to an unimplemented method in the TUN-device on macOS based clients.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Ensure that annotation enabled traffic-agents are uninstall when uninstalling the traffic-manager.</div></div>\n<div style=\"margin-left: 15px\">\n\nA traffic-agent injected because the workload had the inject annotation enabled would sometimes not get uninstalled when the traffic-manager was uninstalled.\n</div>\n\n## Version 2.21.2 <span style=\"font-size: 16px;\">(January 26)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix panic when agentpf.client creates a Tunnel</div></div>\n<div style=\"margin-left: 15px\">\n\nA race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period,\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix goroutine leak in dialer.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe context passed to the `Tunnel` call that creates a stream for a dialer, was not cancelled when the dialer was finished, so the stream was never properly closed, leading to one dormant goroutine for each stream.\n</div>\n\n## Version 2.21.1 <span style=\"font-size: 16px;\">(December 17)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Allow ingest of serverless deployments without specifying an inject-container-ports annotation](https://github.com/telepresenceio/telepresence/issues/3741)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe ability to intercept a workload without a service is built around the `telepresence.getambassador.io/inject-container-ports` annotation, and it was also required in order to ingest such a workload. This was counterintuitive and the requirement was removed. An ingest doesn't use a port.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Upgrade module dependencies to get rid of critical vulnerability.</div></div>\n<div style=\"margin-left: 15px\">\n\nUpgrade module dependencies to latest available stable. This includes upgrading golang.org/x/crypto, which had critical issues, from 0.30.0 to 0.31.0 where those issues are resolved.\n</div>\n\n## Version 2.21.0 <span style=\"font-size: 16px;\">(December 13)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Automatic VPN conflict avoidance](reference/vpn)</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence not only detects subnet conflicts between the cluster and workstation VPNs but also resolves them by performing network address translation to move conflicting subnets out of the way.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Virtual Address Translation (VNAT).](reference/vpn)</div></div>\n<div style=\"margin-left: 15px\">\n\nIt is now possible to use a virtual subnet without routing the affected IPs to a specific workload. A new `telepresence connect --vnat CIDR` flag was added that will perform virtual network address translation of cluster IPs. This flag is very similar to the `--proxy-via CIDR=WORKLOAD` introduced in 2.19, but without the need to specify a workload.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Intercepts targeting a specific container](reference/engagements/container)</div></div>\n<div style=\"margin-left: 15px\">\n\nIn certain scenarios, the container owning the intercepted port differs from the container the intercept targets. This port owner's sole purpose is to route traffic from the service to the intended container, often using a direct localhost connection.\nThis update introduces a `--container <name>` option to the intercept command. While this option doesn't influence the port selection, it guarantees that the environment variables and mounts propagated to the client originate from the specified container. Additionally, if the `--replace` option is used, it ensures that this container is replaced.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[New telepresence ingest command](howtos/intercepts#ingest-your-service)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new `telepresence ingest` command, similar to `telepresence intercept`, provides local access to the volume mounts and environment variables of a targeted container. However, unlike `telepresence intercept`, `telepresence ingest` does not redirect traffic to the container and ensures that the mounted volumes are read-only.\nAn ingest requires a traffic-agent to be installed in the pods of the targeted workload. Beyond that, it's a client-side operation. This allows developers to have multiple simultaneous ingests on the same container.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[New telepresence curl command](reference/docker-run#the-telepresence-curl-command)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new `telepresence curl` command runs curl from within a container. The command requires that a connection has been established using `telepresence connect --docker`, and the container that runs `curl` will share the same network as the containerized telepresence daemon.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[New telepresence docker-run command](reference/docker-run#the-telepresence-docker-run-command)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe new `telepresence docker-run <flags and arguments>` requires that a connection has been established using `telepresence connect --docker` It will perform a `docker run <flags and arguments>` and add the flag necessary to ensure that started container shares the same network as the containerized telepresence daemon.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Mount everything read-only during intercept</div></div>\n<div style=\"margin-left: 15px\">\n\nIt is now possible to append \":ro\" to the intercept `--mount` flag value. This ensures that all remote volumes that the intercept mounts are read-only.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Unify client configuration](reference/config)</div></div>\n<div style=\"margin-left: 15px\">\n\nPreviously, client configuration was divided between the config.yml file and a Kubernetes extension. DNS and routing settings were initially found only in the extension. However, the Helm client structure allowed entries from both.\nTo simplify this, we've now aligned the config.yml and Kubernetes extension with the Helm client structure. This means DNS and routing settings are now included in both. The Kubernetes extension takes precedence over the config.yml and Helm client object.\nWhile the old-style Kubernetes extension is still supported for compatibility, it cannot be used with the new style.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Use WebSockets for port-forward instead of the now deprecated SPDY.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence will now use WebSockets instead of SPDY when creating port-forwards to the Kubernetes Cluster, and will fall back to SPDY when connecting to clusters that don't support SPDY. Use of the deprecated SPDY can be forced by setting `cluster.forceSPDY=true` in the `config.yml`.\nSee [Streaming Transitions from SPDY to WebSockets](https://kubernetes.io/blog/2024/08/20/websockets-transition/) for more information about this transition.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Make usage data collection configurable using an extension point, and default to no-ops</div></div>\n<div style=\"margin-left: 15px\">\n\nThe OSS code-base will no longer report usage data to the proprietary collector at Ambassador Labs. The actual calls to the collector remain, but will be no-ops unless a proper collector client is installed using an extension point.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add deployments, statefulSets, replicaSets to workloads Helm chart value](reference/engagements/sidecar#disable-workloads)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Helm chart value `workloads` now supports the kinds `deployments.enabled`, `statefulSets.enabled`, `replicaSets.enabled`. and `rollouts.enabled`. All except `rollouts` are enabled by default. The traffic-manager will ignore workloads, and Telepresence will not be able to intercept them, if the `enabled` of the corresponding kind is set to `false`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Improved command auto-completion</div></div>\n<div style=\"margin-left: 15px\">\n\nThe auto-completion of namespaces, services, and containers have been added where appropriate, and the default file auto completion has been removed from most commands.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Docker run flags --publish, --expose, and --network now work with docker mode connections](reference/docker-run#the-telepresence-docker-run-command)</div></div>\n<div style=\"margin-left: 15px\">\n\nAfter establishing a connection to a cluster using `telepresence connect --docker`, you can run new containers that share the same network as the containerized daemon that maintains the connection. This enables seamless communication between your local development environment and the remote services.\nNormally, Docker has a limitation that prevents combining a shared network configuration with custom networks and exposing ports. However, Telepresence now elegantly circumvents this limitation so that a container started with `telepresence docker-run`, `telepresence intercept --docker-run`, or `telepresence ingest --docker-run` can use flags like `--network`, `--publish`, or `--expose`.\nTo achieve this, Telepresence temporarily adds the necessary network to the containerized daemon. This allows the new container to join the same network. Additionally, Telepresence starts extra socat containers to handle port mapping, ensuring that the desired ports are exposed to the local environment.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Prevent recursion in the Telepresence Virtual Network Interface (VIF)](howtos/cluster-in-vm)</div></div>\n<div style=\"margin-left: 15px\">\n\nNetwork problems may arise when running Kubernetes locally (e.g., Docker Desktop, Kind, Minikube, k3s), because the VIF on the host is also accessible from the cluster's nodes. A request that isn't handled by a cluster resource might be routed back into the VIF and cause a recursion.\nThese recursions can now be prevented by setting the client configuration property `routing.recursionBlockDuration` so that new connection attempts are temporarily blocked for a specific IP:PORT pair immediately after an initial attempt, thereby effectively ending the recursion.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Allow Helm chart to be included as a sub-chart</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Helm chart previously had the unnecessary restriction that the .Release.Name under which telepresence is installed is literally called \"traffic-manager\".  This restriction was preventing telepresence from being included as a sub-chart in a parent chart called anything but \"traffic-manager\". This restriction has been lifted.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add Windows arm64 client build</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence client is now available for Windows ARM64. Updated the release workflow files in github actions to build and publish the Windows ARM64 client.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The --agents flag to telepresence uninstall is now the default.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `telepresence uninstall` was once capable of uninstalling the traffic-manager as well as traffic-agents. This behavior has been deprecated for some time now and in this release, the command is all about uninstalling the agents. Therefore the `--agents` flag was made redundant and whatever arguments that are given to the command must be name of workloads that have an agent installed unless the `--all-agents` is used, in which case no arguments are allowed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Performance improvement for the telepresence list command</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `telepresence list` command will now retrieve its data from the traffic-manager, which significantly improves its performance when used on namespaces that have a lot of workloads.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">During an intercept, the local port defaults to the targeted port of the intercepted container instead of 8080.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence mimics the environment of a target container during an intercept, so it's only natural that the default for the local port is determined by the targeted container port rather than just defaulting to 8080.\nA default can still be explicitly defined using the `config.intercept.defaultPort` setting.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Move the telepresence-intercept-env configmap data into traffic-manager configmap.</div></div>\n<div style=\"margin-left: 15px\">\n\nThere's no need for two configmaps that store configuration data for the traffic manager. The traffic-manager configmap is also watched, so consolidating the configuration there saves some k8s API calls.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Tracing was removed.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe ability to collect trace has been removed along with the `telepresence gather-traces` and `telepresence upload-traces` commands. The underlying code was complex and has not been well maintained since its inception in 2022. We have received no feedback on it and seen no indication that it has ever been used.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Remove obsolete code checking the Docker Bridge for DNS</div></div>\n<div style=\"margin-left: 15px\">\n\nThe DNS resolver checked the Docker bridge for messages on Linux. This code was obsolete and caused problems when running in Codespaces.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix telepresence connect confusion caused by /.dockerenv file</div></div>\n<div style=\"margin-left: 15px\">\n\nA `/.dockerenv` will be present when running in a GitHub Codespaces environment. That doesn't mean that telepresence cannot use docker, or that the root daemon shouldn't start.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Cap timeouts.connectivityCheck at 5 seconds.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe timeout value of `timeouts.connectivityCheck` is used when checking if a cluster is already reachable without Telepresence setting up an additional network route. If it is, this timeout should be high enough to cover the delay when establishing a connection. If this delay is higher than a second, then chances are very low that the cluster already is reachable, and if it can, that all accesses to it will be very slow. In such cases, Telepresence will create its own network interface and do perform its own tunneling.\nThe default timeout for the check remains at 500 millisecond, which is more than sufficient for the majority of cases.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Prevent that traffic-manager injects a traffic-agent into itself.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic-manager can never be a subject for an intercept, ingest, or proxy-via, because that means that it injects the traffic-agent into itself, and it is not designed to do that. A user attempting this will now see a meaningful error message.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Don't include pods in the kube-system namespace when computing pod-subnets from pod IPs</div></div>\n<div style=\"margin-left: 15px\">\n\nA user would normally never access pods in the `kube-system` namespace directly, and automatically including pods included there when computing the subnets will often lead to problems when running the cluster locally. This namespace is therefore now excluded in situations when the pod subnets are computed from the IPs of pods. Services in this namespace will still be available through the service subnet.\nIf a user should require the pod-subnet to be mapped, it can be added to the `client.routing.alsoProxy` list in the helm chart.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Let routes belonging to an allowed conflict be added as a static route on Linux.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe `allowConflicting` setting didn't always work on Linux because the conflicting subnet was just added as a link to the TUN device, and therefore didn't get subjected to routing rule used to assign priority to the given subnet.\n</div>\n\n## Version 2.20.3 <span style=\"font-size: 16px;\">(November 18)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Ensure that Telepresence works with GitHub Codespaces](https://github.com/telepresenceio/telepresence/issues/3722)</div></div>\n<div style=\"margin-left: 15px\">\n\nGitHub Codespaces runs in a container, but not as root. Telepresence didn't handle this situation correctly and only started the user daemon. The root daemon was never started.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Mounts not working correctly when connected with --proxy-via](https://github.com/telepresenceio/telepresence/issues/3715)</div></div>\n<div style=\"margin-left: 15px\">\n\nA mount would try to connect to the sftp/ftp server using the original (cluster side) IP although that IP was translated into a virtual IP when using `--proxy-via`.\n</div>\n\n## Version 2.20.2 <span style=\"font-size: 16px;\">(October 21)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Crash in traffic-manager configured with agentInjector.enabled=false</div></div>\n<div style=\"margin-left: 15px\">\n\nA traffic-manager that was installed with the Helm value `agentInjector.enabled=false` crashed when a client used the commands `telepresence version` or `telepresence status`. Those commands would call a method on the traffic-manager that panicked if no traffic-agent was present. This method will now instead return the standard `Unavailable` error code, which is expected by the caller.\n</div>\n\n## Version 2.20.1 <span style=\"font-size: 16px;\">(October 10)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Some workloads missing in the telepresence list output (typically replicasets owned by rollouts).</div></div>\n<div style=\"margin-left: 15px\">\n\nVersion 2.20.0 introduced a regression in the `telepresence list` command, resulting in the omission of all workloads that were owned by another workload. The correct behavior is to just omit those workloads that are owned by the supported workload kinds `Deployment`, `ReplicaSet`, `StatefulSet`, and `Rollout`. Furthermore, the `Rollout` kind must only be considered supported when the Argo Rollouts feature is enabled in the traffic-manager.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Allow comma separated list of daemons for the gather-logs command.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe name of the `telepresence gather-logs` flag `--daemons` suggests that the argument can contain more than one daemon, but prior to this fix, it couldn't. It is now possible to use a comma separated list, e.g. `telepresence gather-logs --daemons root,user`.\n</div>\n\n## Version 2.20.0 <span style=\"font-size: 16px;\">(October  3)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add timestamp to telepresence_logs.zip filename.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence is now capable of easily find telepresence gather-logs by certain timestamp.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Enable intercepts of workloads that have no service.](https://telepresence.io/docs/reference/engagements/cli#intercepting-without-a-service)</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence is now capable of intercepting workloads that have no associated service. The intercept will then target container port instead of a service port. The new behavior is enabled by adding a <code>telepresence.getambassador.io/inject-container-ports</code> annotation where the value is a comma separated list of port identifiers consisting of either the name or the port number of a container port, optionally suffixed with `/TCP` or `/UDP`.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Publish the OSS version of the telepresence Helm chart](https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe OSS version of the telepresence helm chart is now available at ghcr.io/telepresenceio/telepresence-oss, and can be installed using the command:<br/> <code>helm install traffic-manager oci://ghcr.io/telepresenceio/telepresence-oss --namespace ambassador --version 2.20.0</code> The chart documentation is published at <a href=\"https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss\">ArtifactHUB</a>.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Control the syntax of the environment file created with the intercept flag --env-file](https://telepresence.io/docs/reference/environment)</div></div>\n<div style=\"margin-left: 15px\">\n\nA new <code>--env-syntax &lt;syntax&gt;</code> was introduced to allow control over the syntax of the file created when using the intercept flag <code>--env-file &lt;file&gt;</code>. Valid syntaxes are &quot;docker&quot;, &quot;compose&quot;, &quot;sh&quot;, &quot;csh&quot;, &quot;cmd&quot;, and &quot;ps&quot;; where &quot;sh&quot;, &quot;csh&quot;, and &quot;ps&quot; can be suffixed with &quot;:export&quot;.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add support for Argo Rollout workloads.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence now has an opt-in support for Argo Rollout workloads. The behavior is controlled by `workloads.argoRollouts.enabled` Helm chart value. It is recommended to set the following annotation <code>telepresence.getambassador.io/inject-traffic-agent: enabled</code> to avoid creation of unwanted revisions.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Enable intercepts of containers that bind to podIP</div></div>\n<div style=\"margin-left: 15px\">\n\nIn previous versions, the traffic-agent would route traffic to localhost during periods when an intercept wasn't active. This made it impossible for an application to bind to the pod's IP, and it also meant that service meshes binding to the podIP would get bypassed, both during and after an intercept had been made. This is now changed, so that the traffic-agent instead forwards non intercepted requests to the pod's IP, thereby enabling the application to either bind to localhost or to that IP.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Use ghcr.io/telepresenceio instead of docker.io/datawire for OSS images and the telemount Docker volume plugin.</div></div>\n<div style=\"margin-left: 15px\">\n\nAll OSS telepresence images and the telemount Docker plugin are now published at the public registry ghcr.io/telepresenceio and all references from the client and traffic-manager has been updated to use this registry instead of the one at docker.io/datawire.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Use nftables instead of iptables-legacy</div></div>\n<div style=\"margin-left: 15px\">\n\nSome time ago, we introduced iptables-legacy because users had problems using Telepresence with Fly.io where nftables wasn't supported by the kernel. Fly.io has since fixed this, so Telepresence will now use nftables again. This in turn, ensures that modern systems that lack support iptables-legacy will work.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Root daemon wouldn't start when sudo timeout was zero.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe root daemon refused to start when <code>sudo</code> was configured with a <code>timestamp_timeout=0</code>. This was due to logic that first requested root privileges using a sudo call, and then relied on that these privileges were cached, so that a subsequent call using <code>--non-interactive</code> was guaranteed to succeed. This logic will now instead do one single sudo call, and rely solely on sudo to print an informative prompt and start the daemon in the background.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Detect minikube network when connecting with --docker</div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>telepresence connect --docker</code> failed when attempting to connect to a minikube that uses a docker driver because the containerized daemon did not have access to the <code>minikube</code> docker network. Telepresence will now detect an attempt to connect to that network and attach it to the daemon container as needed.\n</div>\n\n## Version 2.19.1 <span style=\"font-size: 16px;\">(July 12)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add brew support for the OSS version of Telepresence.](https://github.com/telepresenceio/telepresence/issues/3609)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe Open-Source Software version of Telepresence can now be installed using the brew formula via <code>brew install telepresenceio/telepresence/telepresence-oss</code>.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add --create-namespace flag to the telepresence helm install command.</div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>--create-namespace</code> (default <code>true</code>) flag was added to the <code>telepresence helm install</code> command. No attempt will be made to create a namespace for the traffic-manager if it is explicitly set to <code>false</code>. The command will then fail if the namespace is missing.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Introduce DNS fallback on Windows.</div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>network.defaultDNSWithFallback</code> config option has been introduced on Windows. It will cause the DNS-resolver to fall back to the resolver that was first in the list prior to when Telepresence establishes a connection. The option is default <code>true</code> since it is believed to give the best experience but can be set to <code>false</code> to restore the old behavior.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Brew now supports MacOS (amd64/arm64) / Linux (amd64)](https://github.com/datawire/homebrew-blackbird/issues/19)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe brew formula can now dynamically support MacOS (amd64/arm64) / Linux (amd64) in a single formula\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to provide an externally-provisioned webhook secret</div></div>\n<div style=\"margin-left: 15px\">\n\nAdded <code>supplied</code> as a new option for <code>agentInjector.certificate.method</code>. This fully disables the generation of the Mutating Webhook's secret, allowing the chart to use the values of a pre-existing secret named <code>agentInjector.secret.name</code>. Previously, the install would fail when it attempted to create or update the externally-managed secret.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Let PTR query for DNS server return the cluster domain.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>nslookup</code> program on Windows uses a PTR query to retrieve its displayed \"Server\" property. This Telepresence DNS resolver will now return the cluster domain on such a query.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add scheduler name to PODs templates.</div></div>\n<div style=\"margin-left: 15px\">\n\nA new Helm chart value <code>schedulerName</code> has been added. With this feature, we are able to define some particular schedulers from Kubernetes to apply some different strategies to allocate telepresence resources, including the Traffic Manager and hooks pods.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Race in traffic-agent injector when using inject annotation</div></div>\n<div style=\"margin-left: 15px\">\n\nApplying multiple deployments that used the <code>telepresence.getambassador.io/inject-traffic-agent: enabled</code> would cause a race condition, resulting in a large number of created pods that eventually had to be deleted, or sometimes in pods that didn't contain a traffic agent.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix configuring custom agent security context</div></div>\n<div style=\"margin-left: 15px\">\n\n-> The traffic-manager helm chart will now correctly use a custom agent security context if one is provided.\n</div>\n\n## Version 2.19.0 <span style=\"font-size: 16px;\">(June 15)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Warn when an Open Source Client connects to an Enterprise Traffic Manager.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe difference between the OSS and the Enterprise offering is not well understood, and OSS users often install a traffic-manager using the Helm chart published at getambassador.io. This Helm chart installs an enterprise traffic-manager, which is probably not what the user would expect. Telepresence will now warn when an OSS client connects to an enterprise traffic-manager and suggest switching to an enterprise client, or use <code>telepresence helm install</code> to install an OSS traffic-manager.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add scheduler name to PODs templates.</div></div>\n<div style=\"margin-left: 15px\">\n\nA new Helm chart value <code>schedulerName</code> has been added. With this feature, we are able to define some particular schedulers from Kubernetes to apply some different strategies to allocate telepresence resources, including the Traffic Manager and hooks pods.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Improve traffic-manager performance in very large clusters.</div></div>\n<div style=\"margin-left: 15px\">\n\n-> The traffic-manager will now use a shared-informer when keeping track of deployments. This will significantly reduce the load on the Kublet in large clusters and therefore lessen the risk for the traffic-manager being throttled, which can lead to other problems.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Kubeconfig exec authentication failure when connecting with --docker from a WSL linux host</div></div>\n<div style=\"margin-left: 15px\">\n\nClusters like Amazon EKS often use a special authentication binary that is declared in the kubeconfig using an <code>exec</code> authentication strategy. This binary is normally not available inside a container. Consequently, a modified kubeconfig is used when <code>telepresence connect --docker</code> executes, appointing a <code>kubeauth </code> binary which instead retrieves the authentication from a port on the Docker host that communicates with another process outside of Docker. This process then executes the original <code>exec</code> command to retrieve the necessary credentials.\nThis setup was problematic when using WSL, because even though <code>telepresence connect --docker</code> was executed on a Linux host, the Docker host available from <code>host.docker.internal</code> that the <code>kubeauth</code> connected to was the Windows host running Docker Desktop. The fix for this was to use the local IP of the default route instead of <code>host.docker.internal</code> when running under WSL..\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix bug in workload cache, causing endless recursion when a workload uses the same name as its owner.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe workload cache was keyed by name and namespace, but not by kind, so a workload named the same as its owner workload would be found using the same key. This led to the workload finding itself when looking up its owner, which in turn resulted in an endless recursion when searching for the topmost owner.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">FailedScheduling events mentioning node availability considered fatal when waiting for agent to arrive.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic-manager considers some events as fatal when waiting for a traffic-agent to arrive after an injection has been initiated. This logic would trigger on events like &quot;Warning FailedScheduling 0/63 nodes are available&quot; although those events indicate a recoverable condition and kill the wait. This is now fixed so that the events are logged but the wait continues.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Improve how the traffic-manager resolves DNS when no agent is installed.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic-manager is typically installed into a namespace different from the one that clients are connected to. It's therefore important that the traffic-manager adds the client's namespace when resolving single label names in situations where there are any agents to dispatch the DNS query to.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Removal of ability import legacy artifact into Helm.</div></div>\n<div style=\"margin-left: 15px\">\n\nA helm install would make attempts to find manually installed artifacts and make them managed by Helm by adding the necessary labels and annotations. This was important when the Helm chart was first introduced but is far less so today, and this legacy import was therefore removed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Docker aliases deprecation caused failure to detect Kind cluster.](https://docs.docker.com/engine/deprecated/#container-short-id-in-network-aliases-field)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe logic for detecting if a cluster is a local Kind cluster, and therefore needs some special attention when using <code>telepresence connect --docker</code>, relied on the presence of <code>Aliases</code> in the Docker network that a Kind cluster sets up. In Docker versions from 26 and up, this value is no longer used, but the corresponding info can instead be found in the new <code>DNSNames</code> field.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Include svc as a top-level domain in the DNS resolver.](https://github.com/telepresenceio/telepresence/issues/2814)</div></div>\n<div style=\"margin-left: 15px\">\n\nIt's not uncommon that use-cases involving Kafka or other middleware use FQNs that end with &quot;svc&quot;. The core-DNS resolver in Kubernetes can resolve such names. With this bugfix, the Telepresence DNS resolver will also be able to resolve them, and thereby remove the need to add &quot;.svc&quot; to the include-suffix list.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to enable/disable the mutating webhook.</div></div>\n<div style=\"margin-left: 15px\">\n\nA new Helm chart boolean value <code>agentInjector.enable</code> has been added that controls the agent-injector service and its associated mutating webhook. If set to <code>false</code>, the service, the webhook, and the secrets and certificates associated with it, will no longer be installed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to mount a webhook secret.</div></div>\n<div style=\"margin-left: 15px\">\n\nA new Helm chart value <code>agentInjector.certificate.accessMethod</code> which can be set to <code>watch</code> (the default) or <code>mount</code> has been added. The <code>mount</code> setting is intended for clusters with policies that prevent containers from doing a <code>get</code>, <code>list</code> or <code>watch</code> of a <code>Secret</code>, but where a latency of up to 90 seconds is acceptable between the time the secret is regenerated and the agent-injector picks it up.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Make it possible to specify ignored volume mounts using path prefix.</div></div>\n<div style=\"margin-left: 15px\">\n\nVolume mounts like <code>/var/run/secrets/kubernetes.io</code> are not declared in the workload. Instead, they are injected during pod-creation and their names are generated. It is now possible to ignore such mounts using a matching path prefix.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Make the telemount Docker Volume plugin configurable</div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>telemount</code> object was added to the <code>intercept</code> object in <code>config.yml</code> (or Helm value <code>client.intercept</code>), so that the automatic download and installation of this plugin can be fully customised.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add option to load the kubeconfig yaml from stdin during connect.</div></div>\n<div style=\"margin-left: 15px\">\n\nThis allows another process with a kubeconfig already loaded in memory to directly pass it to <code>telepresence connect</code> without needing a separate file. Simply use a dash \"-\" as the filename for the <code>--kubeconfig</code> flag.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ability to specify agent security context.</div></div>\n<div style=\"margin-left: 15px\">\n\nA new Helm chart value <code>agent.securityContext</code> that will allow configuring the security context of the injected traffic agent.  The value can be set to a valid Kubernetes securityContext object, or can be set to an empty value (<code>{}</code>) to ensure the agent has no defined security context.  If no value is specified, the traffic manager will set the agent's security context to the same as the first container's of the workload being injected into.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Tracing is no longer enabled by default.</div></div>\n<div style=\"margin-left: 15px\">\n\nTracing must now be enabled explicitly in order to use the <code>telepresence gather-traces</code> command.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Removal of timeouts that are no longer in use</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>config.yml</code> values <code>timeouts.agentInstall</code> and <code>timeouts.apply</code> haven't been in use since versions prior to 2.6.0, when the client was responsible for installing the traffic-agent. These timeouts are now removed from the code-base, and a warning will be printed when attempts are made to use them.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Search all private subnets to find one open for dnsServerSubnet</div></div>\n<div style=\"margin-left: 15px\">\n\nThis resolves a bug that did not test all subnets in a private range, sometimes resulting in the warning, \"DNS doesn't seem to work properly.\"\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Docker aliases deprecation caused failure to detect Kind cluster.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe logic for detecting if a cluster is a local Kind cluster, and therefore needs some special attention when using <code>telepresence connect --docker</code>, relied on the presence of <code>Aliases</code> in the Docker network that a Kind cluster sets up. In Docker versions from 26 and up, this value is no longer used, but the corresponding info can instead be found in the new <code>DNSNames</code> field.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Creation of individual pods was blocked by the agent-injector webhook.</div></div>\n<div style=\"margin-left: 15px\">\n\nAn attempt to create a pod was blocked unless it was provided by a workload. Hence, commands like <code>kubectl run -i busybox --rm --image=curlimages/curl --restart=Never -- curl echo-easy.default</code> would be blocked from executing.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix panic due to root daemon not running.</div></div>\n<div style=\"margin-left: 15px\">\n\nIf a <code>telepresence connect</code> was made at a time when the root daemon was not running (an abnormal condition) and a subsequent intercept was then made, a panic would occur when the port-forward to the agent was set up. This is now fixed so that the initial <code>telepresence connect</code> is refused unless the root daemon is running.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Get rid of telemount plugin stickiness</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>datawire/telemount</code> that is automatically downloaded and installed, would never be updated once the installation was made. Telepresence will now check for the latest release of the plugin and cache the result of that check for 24 hours. If a new version arrives, it will be installed and used.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Use route instead of address for CIDRs with masks that don't allow \"via\"</div></div>\n<div style=\"margin-left: 15px\">\n\nA CIDR with a mask that leaves less than two bits (/31 or /32 for IPv4) cannot be added as an address to the VIF, because such addresses must have bits allowing a \"via\" IP.\nThe logic was modified to allow such CIDRs to become static routes, using the VIF base address as their \"via\", rather than being VIF addresses in their own right.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Containerized daemon created cache files owned by root</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen using <code>telepresence connect --docker</code> to create a containerized daemon, that daemon would sometimes create files in the cache that were owned by root, which then caused problems when connecting without the <code>--docker</code> flag.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Remove large number of requests when traffic-manager is used in large clusters.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic-manager would make a very large number of API requests during cluster start-up or when many services were changed for other reasons. The logic that did this was refactored and the number of queries were significantly reduced.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Don't patch probes on replaced containers.</div></div>\n<div style=\"margin-left: 15px\">\n\nA container that is being replaced by a <code>telepresence intercept --replace</code> invocation will have no liveness-, readiness, nor startup-probes. Telepresence didn't take this into consideration when injecting the traffic-agent, but now it will refrain from patching symbolic port names of those probes.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Don't rely on context name when deciding if a kind cluster is used.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe code that auto-patches the kubeconfig when connecting to a kind cluster from within a docker container, relied on the context name starting with \"kind-\", but although all contexts created by kind have that name, the user is still free to rename it or to create other contexts using the same connection properties. The logic was therefore changed to instead look for a loopback service address.\n</div>\n\n## Version 2.18.0 <span style=\"font-size: 16px;\">(February  9)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Include the image for the traffic-agent in the output of the version and status commands.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe version and status commands will now output the image that the traffic-agent will be using when injected by the agent-injector.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Custom DNS using the client DNS resolver.</div></div>\n<div style=\"margin-left: 15px\">\n\n<p>A new <code>telepresence connect --proxy-via CIDR=WORKLOAD</code> flag was introduced, allowing Telepresence to translate DNS responses matching specific subnets into virtual IPs that are used locally. Those virtual IPs are then routed (with reverse translation) via the pod's of a given workload. This makes it possible to handle custom DNS servers that resolve domains into loopback IPs. The flag may also be used in cases where the cluster's subnets are in conflict with the workstation's VPN.</p> <p>The CIDR can also be a symbolic name that identifies a subnet or list of subnets:</p><table><tbody> <tr><td><code>also</code></td><td>All subnets added with --also-proxy</td></tr> <tr><td><code>service</code></td><td>The cluster's service subnet</td></tr> <tr><td><code>pods</code></td><td>The cluster's pod subnets.</td></tr> <tr><td><code>all</code></td><td>All of the above.</td></tr> </tbody></table>\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Ensure that agent.appProtocolStrategy is propagated correctly.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>agent.appProtocolStrategy</code> was inadvertently dropped when moving license related code fromm the OSS repository the repository for the Enterprise version of Telepresence. It has now been restored.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Include non-default zero values in output of telepresence config view.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>telepresence config view</code> command will now print zero values in the output when the default for the value is non-zero.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Restore ability to run the telepresence CLI in a docker container.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe improvements made to be able to run the telepresence daemon in docker using <code>telepresence connect --docker</code> made it impossible to run both the CLI and the daemon in docker. This commit fixes that and also ensures that the user- and root-daemons are merged in this scenario when the container runs as root.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Remote mounts when intercepting with the --replace flag.</div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>telepresence intercept --replace</code> did not correctly mount all volumes, because when the intercepted container was removed, its mounts were no longer visible to the agent-injector when it was subjected to a second invocation. The container is now kept in place, but with an image that just sleeps infinitely.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Intercepting with the --replace flag will no longer require all subsequent intercepts to use --replace.</div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>telepresence intercept --replace</code> will no longer switch the mode of the intercepted workload, forcing all subsequent intercepts on that workload to use <code>--replace</code> until the agent is uninstalled. Instead, <code>--replace</code> can be used interchangeably just like any other intercept flag.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Kubeconfig exec authentication with context names containing colon didn't work on Windows</div></div>\n<div style=\"margin-left: 15px\">\n\nThe logic added to allow the root daemon to connect directly to the cluster using the user daemon as a proxy for exec type authentication in the kube-config, didn't take into account that a context name sometimes contains the colon \":\" character. That character cannot be used in filenames on windows because it is the drive letter separator.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Provide agent name and tag as separate values in Helm chart</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>AGENT_IMAGE</code> was a concatenation of the agent's name and tag. This is now changed so that the env instead contains an <code>AGENT_IMAGE_NAME</code> and <code>AGENT_INAGE_TAG</code>. The <code>AGENT_IMAGE </code> is removed. Also, a new env <code>REGISTRY</code> is added, where the registry of the traffic- manager image is provided. The <code>AGENT_REGISTRY</code> is no longer required and will default to <code>REGISTRY</code> if not set.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Environment interpolation expressions were prefixed twice.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence would sometimes prefix environment interpolation expressions in the traffic-agent twice so that an expression that looked like <code>$(SOME_NAME)</code> in the app-container, ended up as <code> $(_TEL_APP_A__TEL_APP_A_SOME_NAME)</code> in the corresponding expression in the traffic-agent.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Panic in root-daemon on darwin workstations with full access to cluster network.</div></div>\n<div style=\"margin-left: 15px\">\n\nA darwin machine with full access to the cluster's subnets will never create a TUN-device, and a check was missing if the device actually existed, which caused a panic in the root daemon.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Show allow-conflicting-subnets in telepresence status and telepresence config view.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>telepresence status</code> and <code>telepresence config view</code> commands didn't show the <code>allowConflictingSubnets</code> CIDRs because the value wasn't propagated correctly to the CLI.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">It is now possible use a host-based connection and containerized connections simultaneously.</div></div>\n<div style=\"margin-left: 15px\">\n\nOnly one host-based connection can exist because that connection will alter the DNS to reflect the namespace of the connection. but it's now possible to create additional connections using <code>--docker</code> while retaining the host-based connection.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Ability to set the hostname of a containerized daemon.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe hostname of a containerized daemon defaults to be the container's ID in Docker. You now can override the hostname using <code>telepresence connect --docker --hostname &lt;a name&gt;</code>.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">New <code>--multi-daemon</code>flag to enforce a consistent structure for the status command output.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe output of the <code>telepresence status</code> when using <code>--output json</code> or <code>--output yaml</code> will either show an object where the <code>user_daemon</code> and <code>root_daemon</code> are top level elements, or when multiple connections are used, an object where a <code>connections</code> list contains objects with those daemons. The flag <code>--multi-daemon</code> will enforce the latter structure even when only one daemon is connected so that the output can be parsed consistently. The reason for keeping the former structure is to retain backward compatibility with existing parsers.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Make output from telepresence quit more consistent.</div></div>\n<div style=\"margin-left: 15px\">\n\nA quit (without -s) just disconnects the host user and root daemons but will quit a container based daemon. The message printed was simplified to remove some have/has is/are errors caused by the difference.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix &quot;tls: bad certificate&quot; errors when refreshing the mutator-webhook secret</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>agent-injector</code> service will now refresh the secret used by the <code>mutator-webhook</code> each time a new connection is established, thus preventing the certificates to go out-of-sync when the secret is regenerated.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Keep telepresence-agents configmap in sync with pod states.</div></div>\n<div style=\"margin-left: 15px\">\n\nAn intercept attempt that resulted in a timeout due to failure of injecting the traffic-agent left the <code>telepresence-agents</code> configmap in a state that indicated that an agent had been added, which caused problems for subsequent intercepts after the problem causing the first failure had been fixed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The <code>telepresence status</code> command will now report the status of all running daemons.</div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>telepresence status</code>, issued when multiple containerized daemons were active, would error with &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now fixed so that the command instead reports the status of all running daemons.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The <code>telepresence version</code> command will now report the version of all running daemons.</div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>telepresence version</code>, issued when multiple containerized daemons were active, would error with &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now fixed so that the command instead reports the version of all running daemons.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Multiple containerized daemons can now be disconnected using <code>telepresence quit -s</code></div></div>\n<div style=\"margin-left: 15px\">\n\nA <code>telepresence quit -s</code>, issued when multiple containerized daemons were active, would error with &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now fixed so that the command instead quits all daemons.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The DNS search path on Windows is now restored when Telepresence quits</div></div>\n<div style=\"margin-left: 15px\">\n\nThe DNS search path that Telepresence uses to simulate the DNS lookup functionality in the connected cluster namespace was not removed by a <code>telepresence quit</code>, resulting in connectivity problems from the workstation. Telepresence will now remove the entries that it has added to the search list when it quits.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The user-daemon would sometimes get killed when used by multiple simultaneous CLI clients.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe user-daemon would die with a fatal &quot;fatal error: concurrent map writes&quot; error in the <code>connector.log</code>, effectively killing the ongoing connection.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Multiple services ports using the same target port would not get intercepted correctly.</div></div>\n<div style=\"margin-left: 15px\">\n\nIntercepts didn't work when multiple service ports were using the same container port. Telepresence would think that one of the ports wasn't intercepted and therefore disable the intercept of the container port.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Root daemon refuses to disconnect.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe root daemon would sometimes hang forever when attempting to disconnect due to a deadlock in the VIF-device.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix panic in user daemon when traffic-manager was unreachable</div></div>\n<div style=\"margin-left: 15px\">\n\nThe user daemon would panic if the traffic-manager was unreachable. It will now instead report a proper error to the client.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Removal of backward support for versions predating 2.6.0</div></div>\n<div style=\"margin-left: 15px\">\n\nThe telepresence helm installer will no longer discover and convert workloads that were modified by versions prior to 2.6.0. The traffic manager will and no longer support the muxed tunnels used in versions prior to 2.5.0.\n</div>\n\n## Version 2.17.0 <span style=\"font-size: 16px;\">(November 14)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Additional Prometheus metrics to track intercept/connect activity</div></div>\n<div style=\"margin-left: 15px\">\n\nThis feature adds the following metrics to the Prometheus endpoint: <code>connect_count</code>, <code>connect_active_status</code>, <code>intercept_count</code>, and <code>intercept_active_status</code>. These are labeled by client/install_id. Additionally, the <code>intercept_count</code> metric has been renamed to <code>active_intercept_count</code> for clarity.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Make the Telepresence client docker image configurable.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe docker image used when running a Telepresence intercept in docker mode can now be configured using the setting <code>images.clientImage</code> and will default first to the value of the environment <code> TELEPRESENCE_CLIENT_IMAGE</code>, and then to the value preset by the telepresence binary. This configuration setting is primarily intended for testing purposes.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Use traffic-agent port-forwards for outbound and intercepted traffic.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe telepresence TUN-device is now capable of establishing direct port-forwards to a traffic-agent in the connected namespace. That port-forward is then used for all outbound traffic to the device, and also for all traffic that arrives from intercepted workloads. Getting rid of the extra hop via the traffic-manager improves performance and reduces the load on the traffic-manager. The feature can only be used if the client has Kubernetes port-forward permissions to the connected namespace. It can be disabled by setting <code> cluster.agentPortForward</code> to <code>false</code> in <code>config.yml</code>.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Improve outbound traffic performance.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe root-daemon now communicates directly with the traffic-manager instead of routing all outbound traffic through the user-daemon. The root-daemon uses a patched kubeconfig where <code>exec</code> configurations to obtain credentials are dispatched to the user-daemon. This to ensure that all authentication plugins will execute in user-space. The old behavior of routing everything through the user-daemon can be restored by setting <code>cluster.connectFromRootDaemon</code> to <code>false</code> in <code>config.yml</code>.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">New networking CLI flag --allow-conflicting-subnets</div></div>\n<div style=\"margin-left: 15px\">\n\ntelepresence connect (and other commands that kick off a connect) now accepts an --allow-conflicting-subnets CLI flag. This is equivalent to client.routing.allowConflictingSubnets in the helm chart, but can be specified at connect time. It will be appended to any configuration pushed from the traffic manager.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Warn if large version mismatch between traffic manager and client.</div></div>\n<div style=\"margin-left: 15px\">\n\nPrint a warning if the minor version diff between the client and the traffic manager is greater than three.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The authenticator binary was removed from the docker image.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>authenticator</code> binary, used when serving proxied <code>exec</code> kubeconfig credential retrieval, has been removed. The functionality was instead added as a subcommand to the <code>telepresence </code> binary.\n</div>\n\n## Version 2.16.1 <span style=\"font-size: 16px;\">(October 12)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add --docker-debug flag to the telepresence intercept command.</div></div>\n<div style=\"margin-left: 15px\">\n\nThis flag is similar to <code>--docker-build</code> but will start the container with more relaxed security using the <code>docker run</code> flags <code>--security-opt apparmor=unconfined --cap-add SYS_PTRACE</code>.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add a --export option to the telepresence connect command.</div></div>\n<div style=\"margin-left: 15px\">\n\nIn some situations it is necessary to make some ports available to the host from a containerized telepresence daemon. This commit adds a repeatable <code>--expose &lt;docker port exposure&gt;</code> flag to the connect command.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Prevent agent-injector webhook from selecting from kube-xxx namespaces.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>kube-system</code> and <code>kube-node-lease</code> namespaces should not be affected by a global agent-injector webhook by default. A default <code>namespaceSelector</code> was therefore added to the Helm Chart <code>agentInjector.webhook</code> that contains a <code>NotIn</code> preventing those namespaces from being selected.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Backward compatibility for pod template TLS annotations.</div></div>\n<div style=\"margin-left: 15px\">\n\nUsers of Telepresence &lt; 2.9.0 that make use of the pod template TLS annotations were unable to upgrade because the annotation names have changed (now prefixed by \"telepresence.\"), and the environment expansion of the annotation values was dropped. This fix restores support for the old names (while retaining the new ones) and the environment expansion.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/security.png\" alt=\"security\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Built with go 1.21.3</div></div>\n<div style=\"margin-left: 15px\">\n\nBuilt Telepresence with go 1.21.3 to address CVEs.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Match service selector against pod template labels</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen listing intercepts (typically by calling <code>telepresence list</code>) selectors of services are matched against workloads. Previously the match was made against the labels of the workload, but now they are matched against the labels pod template of the workload. Since the service would actually be matched against pods this is more correct. The most common case when this makes a difference is that statefulsets now are listed when they should.\n</div>\n\n## Version 2.16.0 <span style=\"font-size: 16px;\">(October  2)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">The helm sub-commands will no longer start the user daemon.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>telepresence helm install/upgrade/uninstall</code> commands will no longer start the telepresence user daemon because there's no need to connect to the traffic-manager in order for them to execute.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Routing table race condition</div></div>\n<div style=\"margin-left: 15px\">\n\nA race condition would sometimes occur when a Telepresence TUN device was deleted and another created in rapid succession that caused the routing table to reference interfaces that no longer existed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Stop lingering daemon container</div></div>\n<div style=\"margin-left: 15px\">\n\nWhen using <code>telepresence connect --docker</code>, a lingering container could be present, causing errors like &quot;The container name NN is already in use by container XX ...&quot;. When this happens, the connect logic will now give the container some time to stop and then call <code>docker stop NN</code> to stop it before retrying to start it.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add file locking to the Telepresence cache</div></div>\n<div style=\"margin-left: 15px\">\n\nFiles in the Telepresence cache are accesses by multiple processes. The processes will now use advisory locks on the files to guarantee consistency.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Lock connection to namespace</div></div>\n<div style=\"margin-left: 15px\">\n\nThe behavior changed so that a connected Telepresence client is bound to a namespace. The namespace can then not be changed unless the client disconnects and reconnects. A connection is also given a name. The default name is composed from <code>&lt;kube context name&gt;-&lt;namespace&gt;</code> but can be given explicitly when connecting using <code>--name</code>. The connection can optionally be identified using the option <code>--use &lt;name match&gt;</code> (only needed when docker is used and more than one connection is active).\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Deprecation of global --context and --docker flags.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe global flags <code>--context</code> and <code>--docker</code> will now be considered deprecated unless used with commands that accept the full set of Kubernetes flags (e.g. <code>telepresence connect</code>).\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Deprecation of the --namespace flag for the intercept command.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>--namespace</code> flag is now deprecated for <code>telepresence intercept</code> command. The flag can instead be used with all commands that accept the full set of Kubernetes flags (e.g. <code>telepresence connect</code>).\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Legacy code predating version 2.6.0 was removed.</div></div>\n<div style=\"margin-left: 15px\">\n\nThe telepresence code-base still contained a lot of code that would modify workloads instead of relying on the mutating webhook installer when a traffic-manager version predating version 2.6.0 was discovered. This code has now been removed.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add `telepresence list-namespaces` and `telepresence list-contexts` commands</div></div>\n<div style=\"margin-left: 15px\">\n\nThese commands can be used to check accessible namespaces and for automation.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Implicit connect warning</div></div>\n<div style=\"margin-left: 15px\">\n\nA deprecation warning will be printed if a command other than <code>telepresence connect</code> causes an implicit connect to happen. Implicit connects will be removed in a future release.\n</div>\n\n## Version 2.15.1 <span style=\"font-size: 16px;\">(September  6)</span>\n## <div style=\"display:flex;\"><img src=\"images/security.png\" alt=\"security\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Rebuild with go 1.21.1</div></div>\n<div style=\"margin-left: 15px\">\n\nRebuild Telepresence with go 1.21.1 to address CVEs.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/security.png\" alt=\"security\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Set security context for traffic agent</div></div>\n<div style=\"margin-left: 15px\">\n\nOpenshift users reported that the traffic agent injection was failing due to a missing security context.\n</div>\n\n## Version 2.15.0 <span style=\"font-size: 16px;\">(August 29)</span>\n## <div style=\"display:flex;\"><img src=\"images/security.png\" alt=\"security\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add ASLR to telepresence binaries</div></div>\n<div style=\"margin-left: 15px\">\n\nASLR hardens binary sercurity against fixed memory attacks.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Added client builds for arm64 architecture.](https://github.com/telepresenceio/telepresence/issues/3259)</div></div>\n<div style=\"margin-left: 15px\">\n\nUpdated the release workflow files in github actions to including building and publishing the client binaries for arm64 architecture.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[KUBECONFIG env var can now be used with the docker mode.](https://github.com/telepresenceio/telepresence/pull/3300)</div></div>\n<div style=\"margin-left: 15px\">\n\nIf provided, the KUBECONFIG environment variable was passed to the kubeauth-foreground service as a parameter. However, since it didn't exist, the CLI was throwing an error when using <code>telepresence connect --docker</code>.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix deadlock while watching workloads](https://github.com/telepresenceio/telepresence/pull/3298)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe <code>telepresence list --output json-stream</code> wasn't releasing the session's lock after being stopped, including with a <code>telepresence quit</code>. The user could be blocked as a result.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Change json output of telepresence list command</div></div>\n<div style=\"margin-left: 15px\">\n\nReplace deprecated info in the JSON output of the telepresence list command.\n</div>\n\n## Version 2.14.4 <span style=\"font-size: 16px;\">(August 21)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Nil pointer exception when upgrading the traffic-manager.](https://github.com/telepresenceio/telepresence/issues/3313)</div></div>\n<div style=\"margin-left: 15px\">\n\nUpgrading the traffic-manager using <code>telepresence helm upgrade</code> would sometimes result in a helm error message <q>executing \"telepresence/templates/intercept-env-configmap.yaml\" at &lt;.Values.intercept.environment.excluded&gt;: nil pointer evaluating interface {}.excluded\"</q>\n</div>\n\n## Version 2.14.2 <span style=\"font-size: 16px;\">(July 26)</span>\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Telepresence now use the OSS agent in its latest version by default.](https://github.com/telepresenceio/telepresence/issues/3271)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe traffic manager admin was forced to set it manually during the chart installation.\n</div>\n\n## Version 2.14.1 <span style=\"font-size: 16px;\">(July  7)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Envoy's http idle timout is now configurable.</div></div>\n<div style=\"margin-left: 15px\">\n\nA new <code>agent.helm.httpIdleTimeout</code> setting was added to the Helm chart that controls the proprietary Traffic agent's http idle timeout. The default of one hour, which in some situations would cause a lot of resource consuming and lingering connections, was changed to 70 seconds.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Add more gauges to the Traffic manager's Prometheus client.</div></div>\n<div style=\"margin-left: 15px\">\n\nSeveral gauges were added to the Prometheus client to make it easier to monitor what the Traffic manager spends resources on.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Agent Pull Policy</div></div>\n<div style=\"margin-left: 15px\">\n\nAdd option to set traffic agent pull policy in helm chart.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Resource leak in the Traffic manager.</div></div>\n<div style=\"margin-left: 15px\">\n\nFixes a resource leak in the Traffic manager caused by lingering tunnels between the clients and Traffic agents. The tunnels are now closed correctly when terminated from the side that created them.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fixed problem setting traffic manager namespace using the kubeconfig extension.](https://www.telepresence.io/docs/reference/config#manager)</div></div>\n<div style=\"margin-left: 15px\">\n\nFixes a regression introduced in version 2.10.5, making it impossible to set the traffic-manager namespace using the telepresence.io kubeconfig extension.\n</div>\n\n## Version 2.14.0 <span style=\"font-size: 16px;\">(June 12)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[DNS configuration now supports excludes and mappings.](https://github.com/telepresenceio/telepresence/pull/3172)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe DNS configuration now supports two new fields, excludes and mappings. The excludes field allows you to exclude a given list of hostnames from resolution, while the mappings field can be used to resolve a hostname with another.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Added the ability to exclude environment variables</div></div>\n<div style=\"margin-left: 15px\">\n\nAdded a new config map that can take an array of environment variables that will then be excluded from an intercept that retrieves the environment of a pod.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fixed traffic-agent backward incompatibility issue causing lack of remote mounts</div></div>\n<div style=\"margin-left: 15px\">\n\nA traffic-agent of version 2.13.3 (or 1.13.15) would not propagate the directories under <code>/var/run/secrets</code> when used with a traffic manager older than 2.13.3.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fixed race condition causing segfaults on rare occasions when a tunnel stream timed out.](https://github.com/telepresenceio/telepresence/pull/2963)</div></div>\n<div style=\"margin-left: 15px\">\n\nA context cancellation could sometimes be trapped in a stream reader, causing it to incorrectly return an undefined message which in turn caused the parent reader to panic on a <code>nil</code> pointer reference.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Routing conflict reporting.</div></div>\n<div style=\"margin-left: 15px\">\n\nTelepresence will now attempt to detect and report routing conflicts with other running VPN software on client machines. There is a new configuration flag that can be tweaked to allow certain CIDRs to be overridden by Telepresence.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">test-vpn command deprecated</div></div>\n<div style=\"margin-left: 15px\">\n\nRunning telepresence test-vpn will now print a deprecation warning and exit. The command will be removed in a future release. Instead, please configure telepresence for your VPN's routes.\n</div>\n\n## Version 2.13.3 <span style=\"font-size: 16px;\">(May 25)</span>\n## <div style=\"display:flex;\"><img src=\"images/feature.png\" alt=\"feature\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Add imagePullSecrets to hooks](https://github.com/telepresenceio/telepresence/pull/3079)</div></div>\n<div style=\"margin-left: 15px\">\n\nAdd .Values.hooks.curl.imagePullSecrets and .Values.hooks curl.imagePullSecrets to Helm values.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/change.png\" alt=\"change\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Change reinvocation policy to Never for the mutating webhook</div></div>\n<div style=\"margin-left: 15px\">\n\nThe default setting of the reinvocationPolicy for the mutating webhook dealing with agent injections changed from Never to IfNeeded.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix mounting fail of IAM roles for service accounts web identity token](https://github.com/telepresenceio/telepresence/issues/3166)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe eks.amazonaws.com/serviceaccount volume injected by EKS is now exported and remotely mounted during an intercept.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Correct namespace selector for cluster versions with non-numeric characters](https://github.com/telepresenceio/telepresence/pull/3184)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe mutating webhook now correctly applies the namespace selector even if the cluster version contains non-numeric characters. For example, it can now handle versions such as Major:\"1\", Minor:\"22+\".\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Enable IPv6 on the telepresence docker network](https://github.com/telepresenceio/telepresence/issues/3179)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe \"telepresence\" Docker network will now propagate DNS AAAA queries to the Telepresence DNS resolver when it runs in a Docker container.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix the crash when intercepting with --local-only and --docker-run](https://github.com/telepresenceio/telepresence/issues/3171)</div></div>\n<div style=\"margin-left: 15px\">\n\nRunning telepresence intercept --local-only --docker-run no longer  results in a panic.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[Fix incorrect error message with local-only mounts](https://github.com/telepresenceio/telepresence/issues/3171)</div></div>\n<div style=\"margin-left: 15px\">\n\nRunning telepresence intercept --local-only --mount false no longer results in an incorrect error message saying \"a local-only intercept cannot have mounts\".\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">[specify port in hook urls](https://github.com/telepresenceio/telepresence/pull/3161)</div></div>\n<div style=\"margin-left: 15px\">\n\nThe helm chart now correctly handles custom agentInjector.webhook.port that was not being set in hook URLs.\n</div>\n\n## <div style=\"display:flex;\"><img src=\"images/bugfix.png\" alt=\"bugfix\" style=\"width:30px;height:fit-content;\"/><div style=\"display:flex;margin-left:7px;\">Fix wrong default value for disableGlobal and agentArrival</div></div>\n<div style=\"margin-left: 15px\">\n\nParams .intercept.disableGlobal and .timeouts.agentArrival are now correctly honored.\n</div>\n\n"
  },
  {
    "path": "docs/release-notes.mdx",
    "content": "---\ntitle: Release Notes\n---\n\nimport { Note, Title, Body } from '@site/src/components/ReleaseNotes'\n\n[comment]: # (Code generated by relnotesgen. DO NOT EDIT.)\n\n# Telepresence Release Notes\n## Version 2.27.2 <span style={{fontSize:'16px'}}>(March  9)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/4073\">Fix duplicate Section field in .deb package causing dpkg install failure</Title>\n\t<Body>\nThe <code>.deb</code> package control file contained a duplicate <code>Section</code> field, which caused <code>dpkg -i</code> to fail with a parsing error. The <code>Section</code> field was specified both as a top-level <code>nfpm</code> default and explicitly under <code>deb.fields</code>. Moved to the top-level <code>section</code> key and removed the <code>deb.fields</code> block entirely.\n  </Body>\n</Note>\n## Version 2.27.1 <span style={{fontSize:'16px'}}>(March  8)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/4070\">Fix duplicate Priority field in .deb package causing dpkg install failure</Title>\n\t<Body>\nThe <code>.deb</code> package control file contained a duplicate <code>Priority</code> field, which caused <code>dpkg -i</code> to fail with a parsing error. This was caused by <code>nfpm</code> adding <code>Priority: optional</code> by default while the build configuration also specified it explicitly under <code>deb.fields</code>.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/4067\">Fix ingest command lookup when container name is not specified</Title>\n\t<Body>\nThe traffic manager now properly handles ingest lookups when using <code>telepresence ingest &lt;workload&gt; -- command</code> without specifying a container name. Previously, this would fail for workloads because the lookup couldn't find ingests with empty container names. The traffic manager now provides clearer error messages when a container name is required but not specified, and correctly resolves ingests for single-container workloads automatically.\n  </Body>\n</Note>\n## Version 2.27.0 <span style={{fontSize:'16px'}}>(February 28)</span>\n<Note>\n\t<Title type=\"feature\" docs=\"install/client\">Add macOS package installer with root daemon as a system service</Title>\n\t<Body>\nA new macOS package installer (.pkg) is now available that installs Telepresence with the root daemon configured as a launchd service. This eliminates the need for elevated privileges when using Telepresence, as the daemon starts automatically at boot and runs in the background. Available for both Intel (amd64) and Apple Silicon (arm64) Macs.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"install/client\">Add Linux package installers with root daemon as a system service</Title>\n\t<Body>\nNew Linux package installers (.deb for Debian/Ubuntu and .rpm for Fedora/RHEL) are now available that install Telepresence with the root daemon configured as a systemd service. This eliminates the need for elevated privileges when using Telepresence, as the service is enabled and started automatically during installation. Available for both amd64 and arm64 architectures.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"install/client\">Add Windows installer with root daemon as a system service</Title>\n\t<Body>\nA new Windows installer (.exe) is now available that installs Telepresence with the root daemon configured as a Windows service. The installer bundles WinFSP and SSHFS-Win dependencies for volume mount support, adds Telepresence to the system PATH, and optionally installs the TelepresenceDaemon service. This eliminates the need for elevated privileges when using Telepresence. Currently available for amd64 architecture only due to dependency constraints.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/route-controller\">Add route-controller DaemonSet to prevent routing loops on local clusters</Title>\n\t<Body>\nA new optional <code>route-controller</code> DaemonSet can be deployed alongside the traffic-manager on local Kubernetes clusters (Kind, minikube, k3d, Docker Desktop) to prevent routing loops caused by deleted or non-existent service ClusterIPs. It installs an iptables <code>FORWARD</code> chain <code>DROP</code> rule for the service CIDR on every node, and adds per-IP kernel blackhole routes when a Service is deleted. Enable it with <code>routeController.enabled=true</code> in the Helm chart.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Automatic cache cleanup on version change</Title>\n\t<Body>\nTelepresence now tracks its version in a version.json file in the cache directory. When the CLI detects that the major.minor version differs from the running binary, it automatically quits running daemons and clears stale cache entries (preserving logs). This prevents issues caused by leftover cache files from a previous version. Patch and pre-release version changes do not trigger a cache cleanup.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/4053\">Cluster DNS not injected into containers started by telepresence compose</Title>\n\t<Body>\nWhen using <code>telepresence compose up</code>, cluster hostnames did not resolve inside the compose  container because the daemon DNS IP and the tel2-search DNS search domain were not being added to the generated compose spec. DNS and dns_search are now correctly set for all engaged compose services.\n  </Body>\n</Note>\n## Version 2.26.2 <span style={{fontSize:'16px'}}>(February 14)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/4048\">Dial 127.0.0.1 instead of 0.0.0.0 when connecting to local daemons</Title>\n\t<Body>\nWhen a VPN routes all private network addresses, dialing 0.0.0.0 gets routed through the VPN instead of reaching the locally running daemon. This causes Telepresence to report that no daemon is running even though the processes are active and listening. The client now dials 127.0.0.1 explicitly, and the Docker-published daemon port is bound to 127.0.0.1 to match.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix HTTP intercepts via telepresence compose failing when httpFilters or httpPaths are set</Title>\n\t<Body>\nThe compose extension set HeaderFilters and PathFilters on the intercept spec but left the mechanism as \"tcp\". This caused the traffic manager to reject the intercept with \"global TCP/UDP intercepts are disabled\". The mechanism is now correctly switched to \"http\" when any HTTP filters are specified, matching the behavior of the CLI intercept command.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/4049\">Fix \"root daemon is embedded\" error on Windows elevated terminals</Title>\n\t<Body>\nWhen running Telepresence in an elevated (administrator) terminal on Windows, commands like connect and loglevel failed with \"root daemon is embedded\". The user daemon now correctly delegates to the in-process root daemon session instead of returning an error.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/4056\">Fix h2c (HTTP/2 cleartext) prior knowledge not working on transport</Title>\n\t<Body>\nSince Go 1.24, having both HTTP1 and UnencryptedHTTP2 enabled on an HTTP transport causes Go to default to HTTP/1.1 instead of using h2c prior knowledge. The transport now explicitly disables HTTP1 when UnencryptedHTTP2 is enabled, fixing h2c communication for both the default forwarding handler and intercepted traffic.\n  </Body>\n</Note>\n## Version 2.26.1 <span style={{fontSize:'16px'}}>(January 26)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/4043\">Add support for \"warning\" as an alias for \"warn\" in log levels</Title>\n\t<Body>\nThe \"warning\" alias for \"warn\" was the only acceptable value in the Helm chart, yet the traffic manager didn't accept it. This is now fixed so that both names are accepted by the Helm chart and by the traffic manager.\n  </Body>\n</Note>\n## Version 2.26.0 <span style={{fontSize:'16px'}}>(January 23)</span>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/engagements/conflicts\">Add ability for cluster admins to revoke other users' intercepts.</Title>\n\t<Body>\nThe traffic-manager can now execute commands defined in the traffic-manager ConfigMap. As a result, clients that are authorized to\nupdate this ConfigMap can issue those commands.\n\nThis mechanism lays the groundwork for distinguishing administrators from regular users in Telepresence. Administrators can be\ngranted RBAC permissions that allow them to update the traffic-manager ConfigMap, while regular users cannot.\n\nThe new command, `telepresence revoke <intercept-id>`, uses this mechanism to revoke the intercept associated with the specified\nID.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/engagements/conflicts\">Add support for overriding intercepts owned by inactive clients</Title>\n\t<Body>\nIntroduce the Helm chart setting `intercept.inactiveBlockTimeout` that controls the maximum amount of time an intercept may be held by a client that is unreachable or inactive. Once this timeout is exceeded, the intercept no longer blocks conflicting intercepts and may be automatically removed when another client attempts to create a conflicting intercept.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add support for sudo-rs</Title>\n\t<Body>\nTelepresence now supports running the root daemon using `sudo-rs`. This is necessary on Ubuntu where `sudo-rs` has become the default for `sudo`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/cluster_config#restricting_global_intercepts\">Add configuration to disable global TCP/UDP intercepts</Title>\n\t<Body>\nA new Helm chart configuration option `intercept.allowGlobalIntercepts` has been added to control whether global TCP/UDP intercepts are permitted. When set to `false`, only HTTP intercepts with header or path filters are allowed, preventing users from creating global intercepts that block other developers from intercepting the same port. This is particularly useful in shared development environments where multiple developers need to work on the same service simultaneously. The setting defaults to `true` to maintain full backward compatibility with existing deployments. When a user attempts to create a global intercept while the setting is disabled, they receive a helpful error message suggesting the use of `--http-header` or `--http-path-*` flags for HTTP-filtered intercepts.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Enhanced Traffic Manager Startup Reliability</Title>\n\t<Body>\nThe Traffic Manager deployment now includes a `startupProbe` that accurately detects when the service is fully initialized and\nready to handle traffic. This enhancement brings the following benefits:\n\n- **Prevents Premature Traffic Routing**: The manager only reports itself as ready after all configurations are loaded,\neliminating potential race conditions\n- **Smoother Upgrades and Rollouts**: Deployment orchestration tools can reliably determine when the Traffic Manager is\noperational, improving overall installation stability\n\nThis change is particularly beneficial in large clusters or complex networking environments where initialization may take longer\nthan expected.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Support customizable daemon config file</Title>\n\t<Body>\nThe config file for Telepresence is now configurable through the command-line flag `--config`. The `--config <agent config>` flag of the `telepresence genyaml` command was renamed to `--agent` to avoid confusion.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Support customizable daemon log file paths</Title>\n\t<Body>\nLog file paths for the Telepresence daemons are now configurable through the command-line flag `--logfile` that denotes a custom log file location or redirect of the log output to stdout/stderr. Two new log-level configuration entries for `cli` and `kubeAuthDaemon` are also introduced, expanding the existing log-level controls beyond just `userDaemon` and `rootDaemon`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to exclude or include modifications made by other injectors when injecting the traffic agent.</Title>\n\t<Body>\nThe Traffic Agent now has a new configuration option `agentInjector.mutationAware` that can be set to `false` to exclude modifications made by other injectors when injecting the traffic agent. Setting `agentInjector.mutationAware=true` requires `agentInjector.webhook.reinvocationPolicy=IfNeeded`. The default setting is `true`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to disable the Traffic Agent's HTTP2/Clear-Text probing.</Title>\n\t<Body>\nThe Traffic Agent now has a new configuration option `agent.enableH2cProbing` that can be set to `false` to disable the HTTP2/Clear-Text probing. The default setting is `true` to preserve backwards compatibility, but this will be changed to `false` in a future release.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to configure the Traffic Agent's retry interval for watching intercepts.</Title>\n\t<Body>\nThe Traffic Agent's retry interval when it establishes its watcher for intercepts is now configurable using the Helm chart value `agent.watchRetryInterval`. The default retry interval was also increased from 2 seconds to 10 seconds to improve resilience when connections to the traffic manager are lost.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Make traffic-agent consumption metrics reporting optional.</Title>\n\t<Body>\nMetrics reporting for the traffic-agent can now be optionally enabled or disabled via the `agent.enableConsumptionMetrics` Helm chart value. The traffic-agent metrics will also be completely disabled when the Helm chart value `prometheus.port` is unset or zero. This change is intended to reduce the amount of unnecessary traffic generated by the traffic-agent when consumption metrics reporting is of no interest to the user.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Improved efficiency of traffic manager map updates.</Title>\n\t<Body>\nThe watchable map has been refactored into a client/server model that supports delta-based updates. Where supported, gRPC now transmits only incremental changes instead of full snapshots, significantly reducing payload sizes. This improvement is especially important for large clusters, where full snapshots can be sizable and costly to transmit. Full snapshot streaming remains available as a backward-compatible fallback for clients that do not support delta methods.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Use TCP/IP instead of Unix sockets for all communication between local processes.</Title>\n\t<Body>\nTelepresence now uses TCP/IP sockets for all communication between local processes. This change reduces the risk of socket-related errors caused by lingering sockets from previous Telepresence sessions. It also eliminates the difficulties of using Unix sockets for communication between a system service and user processes on Windows.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Don't allow connect with --docker when client is configured with intercept.useFtp=true</Title>\n\t<Body>\nThe `--docker` flag is not allowed when the client is configured with `intercept.useFtp=true` and an error is now generated\ninstantly by the `telepresence connect --docker` command.\n\nThe docker volume plugin cannot use FTP because it requires two ports: a fixed control port that Telepresence can proxy, and a\ndynamic data port (randomly chosen during connection) that Telepresence cannot proxy on-demand. The port-forwarder only forwards\npre-configured ports and doesn't understand FTP's protocol. Consequently, FTP isn't allowed in this scenario. If it was, then when\nan FTP server tells the client to use an unpredictable second port for file transfers, Telepresence would block it—causing the\nconnection to fail every time.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Better names for the Telepresence Daemons</Title>\n\t<Body>\nUsing the name xxx-foreground isn't very intuitive when talking about daemon processes. Yes, the command, when issued in a\nterminal, will start the daemon in foreground so the names of the commands does have some logic to them, but then again, starting\nin the foreground is the default behavior of any command. And when the same command is started from the CLI, it will be started in\nthe background, despite its name.\n\nThe daemons are therefore now renamed:\n\n- connector-foreground =&gt; userd\n- daemon-foreground =&gt; rootd\n- kubeauth-foreground =&gt; kubeauthd\n\nThis also affects the Telepresence hidden CLI commands `telepresence daemon-foreground` and `telepresence connector-foreground`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Add retry logic for tunnel connection attempts</Title>\n\t<Body>\nThe tunnel dialer now uses a backoff-based retry mechanism when establishing connections. This ensures that dialing attempts for both TCP and UDP protocols persist if the target IP is not immediately ready to receive requests.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Retry mechanism for client tunnel creation</Title>\n\t<Body>\nThe traffic agent now includes a backoff-based retry mechanism when establishing tunnel streams to a client. This prevents \"no dial watcher\" connection failures caused by a race condition where the tunnel request arrives before the client has fully initialized its communication channel.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix \"close of closed channel\" panic in the root daemon process.</Title>\n\t<Body>\nThe root daemon process would sometimes panic with \"close of closed channel\" due to a race condition in the DNS cache logic. This issue has been fixed.\n  </Body>\n</Note>\n## Version 2.25.2 <span style={{fontSize:'16px'}}>(December 26)</span>\n<Note>\n\t<Title type=\"bugfix\">Ensure that the exit code from a docker command becomes the exit code of the Telepresence command.</Title>\n\t<Body>\nWhen running a Docker command using `telepresence docker-run` or `telepresence curl`, the exit code would be 1 for all non-zero exit codes from the Docker command. This has been fixed so that the exit code from the Docker command becomes the exit code of the Telepresence command.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix a bug causing truncation of command text when generating external command help.</Title>\n\t<Body>\nThe Telepresence CLI would truncate the command text when generating help for external commands such as `docker compose` that had text spanning more than one line. This has been fixed so that the full command text is displayed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix schema for agent.image.pullSecrets</Title>\n\t<Body>\nThe `agent.image.pullSecrets` is referenced by the helm chart's deployment.yaml but was previously disallowed by the schema file.\n  </Body>\n</Note>\n## Version 2.25.1 <span style={{fontSize:'16px'}}>(November 10)</span>\n<Note>\n\t<Title type=\"bugfix\">Volumes did not mount correctly when using `telepresence connect --docker` when Docker had IPv6 enabled.</Title>\n\t<Body>\nTelepresence failed to mount volumes after connecting with `telepresence connect --docker` when Docker Engine had IPv6 enabled in its default bridge network. Disabling IPv6 in the Telepresence client configuration did not resolve the issue. This was fixed in Telepresence Volume Plugin \"telemount\" version 0.3.2, which circumvented a [bug in sshfs](https://github.com/libfuse/sshfs/issues/335). Additionally, the volume plugin will no longer use IPv6 when the client configuration `docker.enableIPv6` is set to `false`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Remove unnecessary setcap from traffic binary</Title>\n\t<Body>\nThe setcap capability (cap_net_bind_service) was removed from the traffic binary build process. This capability was originally added to allow the binary to bind to privileged ports, specifically port 443 for the mutating webhook. Since version 2.24.0, the default mutating webhook port was changed to 8443 (a non-privileged port), making this capability unnecessary. Removing it simplifies the build process and reduces the security surface area.\n  </Body>\n</Note>\n## Version 2.25.0 <span style={{fontSize:'16px'}}>(October 16)</span>\n<Note>\n\t<Title type=\"feature\" docs=\"howtos/engage#intercept-your-application\">HTTP Intercepts with HTTP header and path filtering</Title>\n\t<Body>\nTelepresence now supports HTTP Intercepts, enabling fine-grained HTTP traffic filtering for intercepts.\nUsers can intercept only specific HTTP requests based on headers and URL paths using the new `--http-header`,\n`--http-path-prefix`, `--http-path-equal`, and `--http-path-regex` flags. This allows multiple developers\nto work on the same service simultaneously by intercepting only their specific traffic patterns, rather than\nintercepting all traffic to a service.\n\n**Routing Precedence Model**: Header-based intercepts take priority over path-only intercepts. When multiple\nintercepts are active on the same workload, requests are evaluated against header-based filters first, then\npath-only filters. This enables different developers to use header-based personal intercepts (e.g.,\n`x-user=alice`) while others use path-based intercepts (e.g., `/admin/*`) without conflicts.\n\n**Conflict Detection**: Intercepts conflict only when their filters would route the same traffic to different\ndestinations. Key rules:\n\n- Different header values (e.g., `X-User=adam` vs `X-User=bertil`) do NOT conflict\n- Header filters use subset logic: `X-User=adam` conflicts with `X-User=adam, X-Session=123` (first is subset)\n- Same headers with different paths do NOT conflict: `X-User=adam + /api/*` vs `X-User=adam + /admin/*`\n- Path-only intercepts operate at a lower priority tier than header-based intercepts\n\nThe feature maintains full backward compatibility with existing TCP intercepts.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"howtos/mtls\">TLS/mTLS Intercept Support</Title>\n\t<Body>\nSupport was added for HTTP-filtered intercepts on applications using TLS/mTLS encryption. The new functionality enables\nTelepresence to decrypt and inspect encrypted traffic by accessing TLS certificates, facilitating debugging and testing of secure\napplications.\n\nCertificates can be accessed via mounted volumes or Kubernetes secrets in the same namespace. New annotations\n(`telepresence.io/downstream-cert-path` and `telepresence.io/downstream-cert-secret`) allow configuration of certificate paths or\nsecrets for decrypting traffic on specified ports. For mTLS, the `telepresence.io/upstream-cert-` annotation prefix supports\nre-encryption of upstream traffic using client-side certificates.\n\nFor self-signed certificates, the `telepresence.io/upstream-insecure-skip-verify` annotation bypasses verification, enabling\nHTTP-filtered intercepts in development environments. The `--plaintext` option allows unencrypted traffic during intercepts or\nwiretaps.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add MCP server to Telepresence CLI</Title>\n\t<Body>\nThe Telepresence CLI now includes a lightweight MCP server that can be used to allow local AI agents to execute some CLI commands, such as connecting to a traffic manager, listing interceptable apps, and creating an intercept. The server can be enabled using the new `telepresence mcp claude enable` or `telepresence mcp vscode enable` commands.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Enhance Resilience of Engagements During Traffic-Manager Redeploys</Title>\n\t<Body>\nThe telepresence client and traffic-agent now automatically reconnect to the traffic-manager after a restart. Upon reconnection, they share their current state, ensuring ongoing engagements remain uninterrupted. This improvement minimizes user impact during traffic-manager upgrades.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add support for IPv6 and dual-stack when using `telepresence connect --docker`</Title>\n\t<Body>\nTelepresence now supports using `telepresence connect --docker` together with Kubernetes single-stack IPv4\nnetworking, single-stack IPv6 networking, or dual-stack networking with both network families. Both are\nenabled by default, but can be disabled by setting the `client.docker.enableIPv4` or `client.docker.enableIPv6`\nto false in the Helm chart, or by using the corresponding settings `docker.enableIPv4` or `docker.enableIPv6`\nthe client configuration file.\n\nThe new dual-stack support requires the teleroute network plugin 0.4.0 or later. The client will install this\nversion automatically unless you work in an air-gapped environment.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/restapi\">RESTful API Service Reintroduced with HTTP Filtering Support</Title>\n\t<Body>\nThe Telepresence RESTful API service has been restored with enhanced support for HTTP header and path filtering. This service enables workloads to programmatically query whether they should handle requests based on active intercepts. Added `--metadata` flag allows attaching custom metadata to intercepts that can be retrieved through the API endpoints. The API server is now accessible via `TELEPRESENCE_API_HOST` and `TELEPRESENCE_API_PORT` environment variables in both cluster pods and local intercept handlers.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">More efficient DNS handling in the traffic-manager</Title>\n\t<Body>\nTelepresence will no longer send DNS queries for A and AAAA records to the traffic-manager. Instead, it will\nsend a single query for the name and then derive the record type from the type of IP address (IPv4 or IPv6) in\nthe response. This reduces the number of DNS queries sent to the cluster's DNS server and makes the behavior\nmore consistent with the `net.LookupNetIP` function in the Go standard library, which the traffic-manager\nultimately uses.\n\nThe lookups will now be performed exclusively by the traffic-manager, never by the traffic-agent. This means\nthat traffic-agents with special DNS configurations might stop working. If this is a problem, the old behavior\ncan be restored by setting the `client.dns.useComplexLookup` parameter in the Helm chart or the\n`dns.useComplexLookup` parameter in the client configuration file.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Updated Helm chart to include keywords and the source repository URL.</Title>\n\t<Body>\nImproves the Helm chart's discoverability on platforms like Artifact Hub and automatically adds a direct link to the source code for users, providing better context.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Telepresence client now requires a traffic manager version of at least 2.21.0.</Title>\n\t<Body>\nThe traffic manager is now required to be at least version 2.21.0. Versions earlier than 2.21.0 will no longer work. The reason for this is that implementing the new reconnect behavior would require too much conditional code with older traffic-managers, and a lot of functionality wouldn't work anyway.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Build binaries and docker images that are stripped from dwarf and debug info.</Title>\n\t<Body>\nThe Telepresence binaries and docker images are now built with the `-w -s` flags, which strip the debug symbols and the DWARF information. This reduces the size of the binaries and docker images by about 50MB. Debug binaries can be built using `DEBUG=1 make build`.\n  </Body>\n</Note>\n## Version 2.24.1 <span style={{fontSize:'16px'}}>(September  5)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3956\">Fix invalid filename generated by telepresence gather-logs command</Title>\n\t<Body>\nThe `telepresence gather-logs` command would generate a filename that was invalid on Windows unless the user specified the filename explicitly using the `--output-file` flag. This was fixed using a more condensed format for the timestamp in the filename.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">A `telepresence connect --docker` fails kubeconfig points to a port-forwarded localhost</Title>\n\t<Body>\nTelepresence would fail to connect to a cluster from within a container if the kubeconfig pointed to a port-forwarded localhost because that localhost is not reachable from within the container. This situation is now detected so that the address used from within the container has \"localhost\" replaced with \"host.docker.internal\", or an alternative alias configured by the user using the new `docker.hostGateway` parameter in the client configuration.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">A `telepresence connect --docker` would fail with some k3s configurations</Title>\n\t<Body>\nTelepresence would fail to connect to a k3s cluster unless the cluster's IP was a loopback address. This is now changed so that any IP:port combination is accepted as long as a container can be found that defines a mapping for it.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/pull/3953\">Restore default value for agent-state.yaml in the traffic-manager configmap</Title>\n\t<Body>\nThe value was previously an empty string which caused problems when when Argo CD tried to synchronize it.\n  </Body>\n</Note>\n## Version 2.24.0 <span style={{fontSize:'16px'}}>(August 25)</span>\n<Note>\n\t<Title type=\"feature\" docs=\"howtos/docker-compose\">Support for Docker Compose</Title>\n\t<Body>\nTelepresence now supports integration with Docker Compose. It connects to and interacts with cluster resources by utilizing `x-tele` extensions within a Docker Compose specification. These extensions configure your local services to effectively act as handlers for Telepresence connections, providing them with the necessary access to the traffic, volumes, and environment of the engaged container.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/cli/telepresence_serve\">Serve up a web-page with telepresence serve.</Title>\n\t<Body>\nA new `telepresence serve <service>` command was added that starts a web browser on the specified service. The command is especially useful when used in combination with `telepresence connect --docker` because it will then expose the given service on a random port on localhost.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to optionally clean up sidecars that have been idle above a specified duration</Title>\n\t<Body>\nAdded configuration parameter `agent.maxIdleTime` to the Helm Chart, to control how long a sidecar can be idle before it is cleaned up. The updating of latestEngagementTime is done every 1m in the Remain Call, which is now called every 1min, compared to every 5s in the past. The agent state (containing latestEngagementTime) is updated in memory, and lazily persisted to the traffic-manager configmap every 2min. Removal of the sidecar is also done in the Remain Call, if the sidecar has been idle for longer than the configured `agent.maxIdleTime`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"https://github.com/telepresenceio/telepresence/issues/3491\">Add option to drop client label in prometheus metrics for GDPR compliance</Title>\n\t<Body>\nThe Helm Chart now has a `prometheus.dropClientLabel` option that can be set to true to drop the client label from the prometheus metrics. This is useful for GDPR compliance, as the client label contains personal data, which can be potentially problematic, i.e allowing the ability to track the working times of an individual.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"https://github.com/telepresenceio/telepresence/issues/3920\">Prefix metrics with \"telepresence_\"</Title>\n\t<Body>\nAvoids metric conflicts and makes these more explicit to improve search in observability stacks.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/cli/telepresence\">CLI documentation in markdown format</Title>\n\t<Body>\nThe Telepresence CLI is now capable of generating its own documentation in markdown format using the new `telepresence man-pages` command. The generated documentation is included under the heading \"Telepresence CLI\" in the the Telepresence reference documentation.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Service Port Rerouting</Title>\n\t<Body>\nThe telepresence connect command introduces a new `--reroute-remote <host>:<port>:<new-port>[/{tcp|udp}]` flag, allowing users to remap service ports. This feature redirects requests sent to `<host>:<new-port>` to `<host>:<port>` within the Telepresence VIF. The flag can be repeated.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Local Port Rerouting</Title>\n\t<Body>\nThe telepresence connect command introduces a new `--reroute-local <local-port>:<host>:<port>[/{tcp|udp}]` flag, allowing users to redirect requests sent to ports on localhost to arbitrary service ports. This feature enables requests sent to `localhost:<local-port>` to be redirected to `<host:port>`. The flag can be repeated.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/inside-container#kubernetes-auth-plugins\">Add information about using Kubernetes auth plugins when using Telepresence CLI in a container</Title>\n\t<Body>\nKubernetes, and hence the Telepresence CLI, must have access to auth plugins declared in the kubeconfig. A section was added to the documentation explaining how to achieve this when using the Telepresence CLI in a container.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add log directory to the output of `telepresence config view`</Title>\n\t<Body>\nThe `telepresence config view` command now includes the path to the directory where the Telepresence logs are stored.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">The default port for the mutating webhook is now 8443. It used to be 443</Title>\n\t<Body>\nPort numbers below 1000 are reserved for privileged processes and are often restricted by firewalls. Consequently, the default port for the mutating webhook was changed from 443 to 8443. You can override this default port using the agentInjector.webhook.port value in the Helm Chart. This change is particularly significant for clusters using Telepresence, where firewall rules limit the admission webhook's access to worker nodes, such as in an Amazon EKS cluster.\n  </Body>\n</Note>\n## Version 2.23.6 <span style={{fontSize:'16px'}}>(July 23)</span>\n<Note>\n\t<Title type=\"bugfix\">Public DNS names aren't resolved from local docker application started by Telepresence</Title>\n\t<Body>\nA container running using `telepresence docker-run` or `telepresence <engage-type> --docker-run` was not able to resolve public DNS names such as \"google.com\". The problem is caused by an undocumented behavior in Docker's internal DNS-resolver when the `--dns` flag is used in conjunction with the teleroute network, where the DNS-server no longer finds public names even thought another bridge network is connected. The problem was solved by using a fallback resolver in the Telepresence DNS resolver.\n  </Body>\n</Note>\n## Version 2.23.5 <span style={{fontSize:'16px'}}>(July 20)</span>\n<Note>\n\t<Title type=\"bugfix\">Let docker.Start pass on --interactive to docker start.</Title>\n\t<Body>\nAn `-i` or `--interactive` flag given when the user runs a container with telepresence must be propagated to `docker start` to attach `stdin`.\n  </Body>\n</Note>\n## Version 2.23.4 <span style={{fontSize:'16px'}}>(July 18)</span>\n<Note>\n\t<Title type=\"bugfix\">Never truncate meaningful output from a command</Title>\n\t<Body>\nThe new human-friendly output using a progress reporter would sometimes truncate error output. This is no longer the case. Instead, all output will be wrapped.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Typo in client mount-policy \"RemoteReadonly\" should be \"RemoteReadOnly\"</Title>\n\t<Body>\nThe Helm Chart correctly expects the remote read-only mount policy to be `RemoteReadOnly`, but the client expected it to be `RemoteReadonly` (without a leading capital letter in the word \"only\").\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3908\">DNS server does not respect semicolons as comments in resolv.conf files</Title>\n\t<Body>\nTelepresence does not work correctly if `/etc/resolv.conf` contains semicolons, which are valid comments as of [linux manpage](https://man7.org/linux/man-pages/man5/resolv.conf.5.html).\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"reference/config#recursioncheck\">Use NXDOMAIN instead SERVFAIL for DNS recursion errors (timeouts)</Title>\n\t<Body>\nA DNS for a single label name that fails in a minikube - or another type of local cluster - will sometimes result in a recursive lookup on the host. Without any type of recursion detection, this lookup will timeout waiting for itself. Previously, this resulted in a `SERVFAIL` from the cluster DNS, which triggered renewed lookup attempts that never stopped. This is now changed so that the same type of timeouts instead results in an `NXDOMAIN` error that doesn't trigger renewed attempts.\nAlso, the recursion check now handles that the cluster's DNS adds suffixes from its search-path.\n  </Body>\n</Note>\n## Version 2.23.3 <span style={{fontSize:'16px'}}>(July  7)</span>\n<Note>\n\t<Title type=\"bugfix\">Fix tunnel channel reuse in traffic-agent to prevent connection failures</Title>\n\t<Body>\nPreviously, when a tunnel became active, the map maintained by the traffic-agent containing channel values for agent-to-client tunnels wasn't cleared. This resulted in closed channels remaining in the map. When port numbers were eventually reused, these stale entries would be discovered and their closed channels would cause immediate stream termination, leading to data loss.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">The -p flags would have no effect in combination with --docker-run</Title>\n\t<Body>\nWhen using `telepresence <engage> --docker-run` with a `-p <port spec>` flag, the Docker driver silently\nignored the port specification. This occurred because the `--network=<teleroute network>` flag disabled both\nadditional network directives and the default bridge network (which is normally used when no network is\nspecified). This has been resolved by:\n\n1. Adding the teleroute network after container creation instead of using a flag\n2. Replacing the single `docker run` command with a sequence of:\n   - `docker create`\n   - Network addition\n   - `docker start`\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Requests lost when using wiretap</Title>\n\t<Body>\nWiretap connection `Close` and `Write` could sometimes be out of sync, so that the `Close` would be executed before the `Write`, causing a \"read/write on closed pipe\" error and loss of data.\n  </Body>\n</Note>\n## Version 2.23.2 <span style={{fontSize:'16px'}}>(June 27)</span>\n<Note>\n\t<Title type=\"bugfix\">Adding an alsoProxy subnet with 32-bit mask no longer works on macOS</Title>\n\t<Body>\nRouting improvements introduced in 2.23.0 surfaced a problem when using special handling of submets with 32-bit masks. The special handling now causes problems on macOS. The logic is now conditioned to only run under linux since it's no longer needed on other operating systems.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">The gather-logs command produces no cluster-side logs when connected with --docker</Title>\n\t<Body>\nThe `telepresence gather-logs` command did not include cluster-side logs when connected using `--docker`, because it tried to store such logs in a temporary directory created by the CLI. The directory was not mounted in the daemon container. This is now changed so that the temporary directory is created under the users cache directory, which is guaranteed to be mounted on the container.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Docker volume mounts failing when connected using both --docker --proxy-via flags</Title>\n\t<Body>\nThe volume mounter would fail when doing a `telepresence connect --docker --proxy-via all=<workload>` followed by an intercept using `--docker-run`, because the bridge that the daemon created would try to access the intercepted pod using its proxied IP. Now, the bridge will instead use the pod's real IP.\n  </Body>\n</Note>\n## Version 2.23.1 <span style={{fontSize:'16px'}}>(June 24)</span>\n<Note>\n\t<Title type=\"feature\">New telepresence helm version command.</Title>\n\t<Body>\nThe new `telepresence helm version` command prints the version of the helm client that is embedded in the telepresence binary.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3861\">Engagement disconnects after certain amount of time</Title>\n\t<Body>\nThe configuration parameter `connectionTTL`, controlling how long a client could be completely idle before\nthe traffic-manager or traffic-agent would consider it dead and disconnect (default 24 hours), had no effect.\nInstead, an engagement would disconnect after 2 hours (the default gRPC `keepAlive.Time` duration). The\ndefault of 24 hours is now reinstated.\n\nThe Helm value `client.connectionTTL` was moved to `grpc.connectionTTL` because it is a server configuration.\nThe old value will still work, but it is deprecated and will be removed eventually.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3887\">Telepresence breaks if config.yml exists but is empty</Title>\n\t<Body>\nTelepresence would refuse to connect with a misleading error if the `config.yml` file containing the client's configuration parameters existed but was empty.\n  </Body>\n</Note>\n## Version 2.23.0 <span style={{fontSize:'16px'}}>(June 17)</span>\n<Note>\n\t<Title type=\"feature\">New telepresence wiretap command</Title>\n\t<Body>\nThe new `telepresence wiretap` command introduces a read-only form of an `intercept` where the original container will run unaffected while a copy of the wiretapped traffic is sent to the client.\nSimilar to an `ingest`, a `wiretap` will always enforce read-only status on all volume mounts, and since that makes the `wiretap` completely read-only, there's no limit to how many simultaneous wiretaps that can be served. In fact, a `wiretap` and an `intercept` on the same port can run simultaneously.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/teleroute\">Add Telepresence Docker Network Plugin \"Teleroute\"</Title>\n\t<Body>\nThe new Teleroute plugin makes it possible for containers to use the Telepresence daemon's VIF without having\nto change their network mode, i.e. a `--network container:<daemon container>` is no longer needed. Instead,\na container can use a custom network created when the Telepresence daemon connects to the cluster.\nThis network uses the new driver \"teleroute\" which is provided by Telepresence.\n\nWith the Teleroute Docker network plugin in place, there's no longer a need for special handling of network\nrelated docker flags, and the following changes have been made:\n\n1. The Teleroute Docker network driver will be installed unless it is already present.\n2. A Teleroute network will be created when starting the Telepresence daemon as a container. This network will\n   then communicate with that container and expose the same CIDRs as the daemon's VIF.\n3. A container started with `telepresence curl`, or\n   `telepresence {ingest|intercept|replace|wiretap} --docker-{run|build|debug}` will no longer change its\n   network mode using `--network container:<daemon container>`, instead it will use\n   `--network <name of teleroute network>`.\n4. As a consequence of #3, published ports and other networks that are added no longer need special handling\n   using socat containers, so all of that has been removed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Control whether the initContainer injection is enabled/disabled</Title>\n\t<Body>\nThe initContainer injection can be optionally disabled by setting the `agent.initContainer.enabled` parameter to false in the `values.yaml` file of the Helm chart. This feature was added to improve compatibility with systems like OpenShift where the initContainer injection cannot be used due to inability to give initContainer NET_ADMIN permissions.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Human friendly progress reporting</Title>\n\t<Body>\nTelepresence now uses a progress reporter that is very similar to the one used by Docker compose. The implementation is a variation of that reporter's source code, so big thanks to the Docker compose CLI authors for making it available as OSS.\nA new global `--progress <progress>` flag was added. It defaults to \"auto\" which means that the style is chosen depending on whether the command runs from a tty type terminal. Other possible values are \"plain\", \"quiet\", and \"json\". `--progress quiet` is implied when formatted output is chosen using `--output json|yaml`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add the ability to use a name for the target host, and defer its resolution</Title>\n\t<Body>\nKnowing the IP of the local service that acts as the handler service for an intercept, replace, or wiretap is not possible until that service has been started, and telepresence will therefore now accept a name for the `--address` flag. The name is not resolved by the daemon until a request is made to the engaged container on a port that is routed to the local service.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add intercept.mountsRoot to the client configuration</Title>\n\t<Body>\nThe new `intercept.mountsRoot` can be set to a directory that will be used as the root for all automatically generated mount directories. The default is to use the platforms temp directory.\nThe setting is not used on windows, where the mounts use drive letters.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add docker.addHostGateway to the client configuration.</Title>\n\t<Body>\nWhen `docker.addHostGateway` is set to `true`, the `docker run` that starts the containerized Telepresence daemon will include the flag `--add-host host.docker.internal:host-gateway`.\nThe flag is set to `true` by default on linux platforms and `false` on other platforms.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Client configuration to override the Helm download URL</Title>\n\t<Body>\nThe default download URL `oci://ghcr.io/telepresenceio/telepresence-oss` used when installing Helm charts with versions that differ from the version of the embedded Helm chart can now be overridden using the client config value `helm.chartURL`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Dropped support for Telepresence legacy flags</Title>\n\t<Body>\nThe `telepresence` CLI command will no longer support legacy flags such as:\n\n- `--swap-deployment`\n- `--new-deployment`\n- `--docker-mount`\n- `--method`\n\nA \"Legacy Telepresence command used\" warning has been printed for several years now, and the mapping for the\n`--swap-deployment` was the `intercept` command, which is very confusing today since we now have the `replace`\ncommand.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Let containerized daemon consistently use the same port for gRPC</Title>\n\t<Body>\nThe port used for the containerized gRPC was randomly selected using the hosts network namespace. This is now changed so that the port used by the container is preset and configurable and then mapped to a random port on the host.\nThe port number can be configured using `grpc.daemonPort` and defaults to `4038`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3875\">Telepresence fails to start the root daemon on Windows unless current user is the administrator</Title>\n\t<Body>\nThe telepresence CLI starts a user daemon and a root daemon. The latter is started using administrator privileges. On a Windows box, this means that the root daemon runs using a different user account (typically \"Administrator\") unless the current user can run processes with elevated privileges. The socket used for communication with the root daemon was assumed to reside in `%USERPROFILE%\\AppData\\Local\\telepresence` and was therefore not found by the CLI and the user daemon. The location will henceforth always be based on the `%USERDATA` of the CLI user.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3873\">Telepresence DNS Fallback stripping CNAME information from DNS Records.</Title>\n\t<Body>\nThe fallback DNS server used on Linux systems without a systemd.resolved configuration, would assume that suffixes belonging to the `search` defined in the `/etc/resolved` had been added by the caller. Since this search path was assumed to be intended for the local machine only, the suffix was stripped off prior to sending the name to the cluster for resolution. This made queries fail that relied on the qualified name to resolve CNAME records. The logic stripping the suffix was therefore removed.\n  </Body>\n</Note>\n## Version 2.22.6 <span style={{fontSize:'16px'}}>(June  3)</span>\n<Note>\n\t<Title type=\"bugfix\">Regression causing \"unexpected slice size\" with older traffic-managers.</Title>\n\t<Body>\nOlder traffic-managers have a different way of reporting the service-subnet. The new way, using a list of subnets reused a proto slice in the GRPC message that was expected to be empty, but older traffic-managers will pass the IP of the kube-dns here. It cannot be parsed as a list of subnets. A check that remedies this mismatch was inserted.\n  </Body>\n</Note>\n## Version 2.22.5 <span style={{fontSize:'16px'}}>(May 29)</span>\n<Note>\n\t<Title type=\"bugfix\">Unable to correctly determine service CIDR with Kubernetes >= 1.33</Title>\n\t<Body>\nStarting with Kubernetes 1.33, the strategy of extracting the cluster's service CIDR from an error message no longer works because the error message has changed. The root cause for this is that Kubernetes introduced the ability to use [Multiple Service CIDRs](https://gist.github.com/aojea/c20eb117bf1c1214f8bba26c495be9c7). Since `ServiceCIDR` is now a resource, it can be easily retrieved (and modified) using standard Kubernetes client API calls, and this is what the traffic manager will use going forward.\nThe fix required an addition to the traffic-manager's RBAC, granting it sufficient permissions to list `networking.k8s.io/servicecidrs`. A future enhancement will allow the traffic manager to watch for service CIDR changes.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Helm chart schema type for nodeSelector was incorrect</Title>\n\t<Body>\nThe Helm chart schema for the `nodeSelector` value was incorrect. Kubernetes defines different types for nodeSelector (inside PodSpec objects) and NodeSelector (inside NodeAffinity, VolumeNodeAffinity and a bunch of other places). The schema was changed to use the correct type. The name `nodeSelector` is still used in the Helm chart so this change is backwards compatible.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Pods with container ports named the same caused intercept to fail</Title>\n\t<Body>\nIntercept container ports now have numbers appended to them if there are multiple ports from multiple containers with the same name. This bugfix works around an issue where Kubernetes allows multiple port definitions in a pod spec to have the same name.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Don't include k8s-defs.json to chart package</Title>\n\t<Body>\nThe k8s-defs.json was unnecessarily included to the Helm chart package and this increased the Helm release secret size so much that it could prevent installation of the Helm chart depending on k8s settings. To fix this k8s-defs.json is not included to the Helm chart anymore.\n  </Body>\n</Note>\n## Version 2.22.4 <span style={{fontSize:'16px'}}>(April 26)</span>\n<Note>\n\t<Title type=\"bugfix\">Don't require internet access when installing the traffic-manager using Helm</Title>\n\t<Body>\nA regression occurred with the introduction of Helm validation in version 2.22.0. The schema relied on external HTTPS links, which inadvertently created a requirement for internet accessibility. To resolve this, we have embedded these resources within the schema, thus removing the need for an internet connection.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Client failed connect with \"failed to exit idle mode\" in the connector.log after being idle</Title>\n\t<Body>\nThe port-forward connections used for connecting the daemon to the traffic-agents were using an incorrect context, causing them to fail after being idle for some time.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix deadlock in Telepresence daemon</Title>\n\t<Body>\nA deadlock would sometimes occur in the Telepresence daemon that prevented it from doing a clean exit during `telepresence quit`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Don't log error message when a pod watcher ends due to cancellation</Title>\n\t<Body>\nErrors printed in the daemon log during normal cancellation of the WatchAgentPods goroutine are now removed.\n  </Body>\n</Note>\n## Version 2.22.3 <span style={{fontSize:'16px'}}>(April  8)</span>\n<Note>\n\t<Title type=\"change\">The Windows install script will now install Telepresence to \"%ProgramFiles%\\telepresence\"</Title>\n\t<Body>\nTelepresence is now installed into \"%ProgramFiles%\\telepresence\" instead of \"C:\\telepresence\".\nThe directory and the Path entry for  `C:\\telepresence` are not longer used and should be removed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3827\">The Windows install script didn't handle upgrades properly</Title>\n\t<Body>\nThe following changes were made:\n\n  - The script now requires administrator privileges\n  - The Path environment is only updated when there's a need for it\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3833\">The Telepresence Helm chart could not be used as a dependency in another chart.</Title>\n\t<Body>\nThe JSON schema validation implemented in Telepresence 2.22.0 had a defect: it rejected the `global` object. This object, a Helm-managed construct, facilitates the propagation of arbitrary configurations from a parent chart to its dependencies. Consequently, charts intended for dependency use must permit the presence of the `global` object.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3831\">Recreating namespaces was not possible when using a dynamically namespaced Traffic Manager</Title>\n\t<Body>\nA shared informer was sometimes reused when namespaces were removed and then later added again, leading to errors like \"handler ... was not added to shared informer because it has stopped already\".\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Single label name DNS lookups didn't work unless at least one traffic-agent was installed</Title>\n\t<Body>\nA problem with incorrect handling of single label names in the traffic-manager's DNS resolver was fixed. The problem would cause lookups like `curl echo` to fail, even though telepresence was connected to a namespace containing an \"echo\" service, unless at least one of the workloads in the connected namespace had a traffic-agent.\n  </Body>\n</Note>\n## Version 2.22.2 <span style={{fontSize:'16px'}}>(March 28)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3828\">Panic when using telepresence replace in a IPv6-only cluster</Title>\n\t<Body>\nA \"slice bounds out of range\" would occur when the targeted Pod's Traffic Agent requested a local dialer to\nbe created on the client. This was due to a glitch in the VPN-tunnel implementation that got triggered when\na remote IPv6-address was combined with a local IPv4-address.\n  </Body>\n</Note>\n## Version 2.22.1 <span style={{fontSize:'16px'}}>(March 27)</span>\n<Note>\n\t<Title type=\"bugfix\">Only restore inactive traffic-agent after a replace.</Title>\n\t<Body>\nA regression in the 2.20.0 release would cause the traffic-agent to be replaced with a dormant version that\ndidn't touch any ports when an intercept ended. This terminated other ongoing intercepts on the same pod.\nThis is now changed so that the traffic-agent remains unaffected for this use-case.\n  </Body>\n</Note>\n## Version 2.22.0 <span style={{fontSize:'16px'}}>(March 14)</span>\n<Note>\n\t<Title type=\"feature\">New telepresence replace command.</Title>\n\t<Body>\nThe new `telepresence replace` command simplifies and clarifies container replacement.\n\nPreviously, the `--replace` flag within the `telepresence intercept` command was used to replace containers.\nHowever, this approach introduced inconsistencies and limitations:\n\n* **Confusion:** Using a flag to modify the core function of a command designed for traffic interception led\n  to ambiguity.\n* **Inaccurate Behavior:** Replacement was not possible when no incoming traffic was intercepted, as the\n  command's design focused on traffic routing.\n\nTo address these issues, the `--replace` flag within `telepresence intercept` has been deprecated. The new\n`telepresence replace` command provides a dedicated and consistent method for replacing containers, enhancing\nclarity and reliability.\n\nKey differences between `replace` and `intercept`:\n\n1. **Scope:** The `replace` command targets and affects an entire container, impacting all its traffic, while\n   an `intercept` targets specific services and/or service/container ports.\n2. **Port Declarations:** Remote ports specified using the `--port` flag are container ports.\n3. **No Default Port:** A `replace` can occur without intercepting any ports.\n4. **Container State:** During a `replace`, the original container is no longer active within the cluster.\n\nThe deprecated `--replace` flag still works, but is hidden from the `telepresence intercept` command help, and\nwill print a deprecation warning when used.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add json-schema for the Telepresence Helm Chart</Title>\n\t<Body>\nHelm can validate a chart using a json-schema using the command `helm lint`, and this schema can be part of the actual Helm chart. The telepresence-oss Helm chart now includes such a schema, and a new `telepresence helm lint` command was added so that linting can be performed using the embedded chart.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">No dormant container present during replace.</Title>\n\t<Body>\nTelepresence will no longer inject a dormant container during a `telepresence replace` operation. Instead, the\nTraffic Agent now directly serves as the replacement container, eliminating the need to forward traffic to the\noriginal application container. This simplification offers several advantages when using the `--replace` flag:\n\n  - **Removal of the init-container:** The need for a separate init-container is no longer necessary.\n  - **Elimination of port renames:** Port renames within the intercepted pod are no longer required.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">One single invocation of the Telepresence intercept command can now intercept multiple ports.</Title>\n\t<Body>\nIt is now possible to intercept multiple ports with one single invocation of `telepresence intercept` by just repeating the `--port` flag.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"install/manager#static-versus-dynamic-namespace-selection\">Unify how Traffic Manager selects namespaces</Title>\n\t<Body>\nThe definition of what namespaces that a Traffic Manager would manage use was scattered into several Helm\nchart values, such as `manager.Rbac.namespaces`, `client.Rbac.namespaces`, and\n`agentInjector.webhook.namespaceSelector`. The definition is now unified to the mutual exclusive top-level\nHelm chart values `namespaces` and `namespaceSelector`.\n\nThe `namespaces` value is just for convenience and a short form of expressing:\n```yaml\nnamespaceSelector:\n  matchExpressions:\n   - key: kubernetes.io/metadata.name\n     operator: in\n     values: <namespaces>.\n```\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Improved control over how remote volumes are mounted using mount policies</Title>\n\t<Body>\nMount policies, that affects how the telepresence traffic-agent shares the pod's volumes, and also how the client will mount them, can now be provided using the Helm chart value `agent.mountPolicies` or as JSON object in the workload annotation `telepresence.io/mount-policies`. A mount policy is applied to a volume or to all paths matching a path-prefix (distinguished by checking if first character is a '/'), and can be one of `Ignore`, `Local`, `Remote`, or `RemoteReadOnly`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">List output includes workload kind.</Title>\n\t<Body>\nThe output of the `telepresence list` command will now include the workload kind (deployment, replicaset, statefulset, or rollout) in all entries.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to override the default securityContext for the Telepresence init-container</Title>\n\t<Body>\nUsers can now use the Helm value `agent.initSecurityContext` to override the default securityContext for the Telepresence init-container.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Let download page use direct links to GitHub</Title>\n\t<Body>\nThe download links on the release page now points directly to the assets on the download page, instead of using being routed from getambassador.io/download/tel2oss/releases.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Use telepresence.io as annotation prefix instead of telepresence.getambassador.io</Title>\n\t<Body>\nThe workload and pod annotations used by Telepresence will now use the prefix `telepresence.io` instead of `telepresence.getambassador.io`. The new prefix is consistent with the prefix used by labels, and it also matches the host name of the documentation site. Annotations using the old name will still work, but warnings will be logged when they are encountered.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Make the DNS recursion check configurable and turn it off by default.</Title>\n\t<Body>\nVery few systems experience a DNS recursion lookup problem. It can only occur when the cluster runs locally and the cluster's DNS is configured to somehow use DNS server that is started by Telepresence. The check is therefore now configurable through the client setting `dns.recursionCheck`, and it is `false` by default.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Trigger the mutating webhook with Kubernetes eviction objects instead of patching workloads.</Title>\n\t<Body>\nTelepresence will now attempt to evict pods in order to trigger the traffic-agent's injection or removal, and revert to patching workloads if evictions are prevented by the pod's disruption budget. This causes a slight change in the traffic-manager RBAC, as the traffic-manager must be able to create \"pod/eviction\" objects.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">The telepresence-agents configmap is no longer used.</Title>\n\t<Body>\nThe traffic-agent configuration was moved into a pod-annotation. This avoids sync problems between the telepresence-agents (which is no no longer present) and the pods.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Drop deprecated current-cluster-id command.</Title>\n\t<Body>\nThe clusterID was deprecated some time ago, and replaced by the ID of the namespace where the traffic-manager is installed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Make telepresence connect --docker work with Rancher Desktop</Title>\n\t<Body>\nRancher Desktop will start a K3s control-plane and typically expose the Kubernetes API server at `127.0.0.1:6443`. Telepresence can connect to this cluster when running on the host, but the address is not available when connecting in docker mode.\nThe problem is solved by ensuring that the Kubernetes API server address used when doing a `telepresence connect --docker` is swapped from 127.0.0.1 to the internal address of the control-plane node. This works because that address is available to other docker containers, and the Kubernetes API server is configured with a certificate that accepts it.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Rename charts/telepresence to charts/telepresence-oss.</Title>\n\t<Body>\nThe Helm chart name \"telepresence-oss\" was inconsistent with its contained folder \"telepresence\". As a result, attempts to install the chart using an argo ApplicationSet failed. The contained folder was renamed to match the chart name.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"install/manager#namespace-collision-detection\">Conflict detection between namespaced and cluster-wide install.</Title>\n\t<Body>\nThe namespace conflict detection mechanism would only discover conflicts between two _namespaced_ Traffic Managers trying to manage the same namespace. This is now fixed so that all types conflicts are discovered.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Don't dispatch DNS discovery queries to the cluster.</Title>\n\t<Body>\nmacOS based systems will often PTR queries using nameslike `b._dns-sd._udp`, lb._dns-sd._udp`, or `db-dns-sd._udp`. Those queries are no longer dispatched to the cluster.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Using the --namespace option with telepresence causes a deadlock.</Title>\n\t<Body>\nUsing `telepresence list --namespace <ns>` with a namespace different from the one that telepresence was connected to, would cause a deadlock, and then produce an empty list.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix problem with exclude-suffix being hidden by DNS search path.</Title>\n\t<Body>\nIn some situations, a name ending with an exclude-suffix like \"xyz.com\" would be expanded by a search path into \"xyz.com.&lt;connected namespace&gt;\" and therefore not be excluded. Instead, the name was sent to the cluster to be resolved, causing an unnecessary load on its DNS server.\n  </Body>\n</Note>\n## Version 2.21.3 <span style={{fontSize:'16px'}}>(February  6)</span>\n<Note>\n\t<Title type=\"bugfix\">Using the --proxy-via flag would sometimes cause connection timeouts.</Title>\n\t<Body>\nTypically, a `telepresence connect --proxy-via <subnet>=<workflow>` would fail with a \"deadline exceeded\" message when several workloads were present in the namespace, the one targeted by the proxy-via didn't yet have an agent installed, and other workloads had an agent. This was due to a race condition in the logic for the agent-based port-forwards in the root daemon. The conditions causing this race are now eliminated.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix panic in root daemon when using the \"allow conflicting subnets\" feature on macOS.</Title>\n\t<Body>\nA regression was introduced in version 2.21.0, causing a panic due to an unimplemented method in the TUN-device on macOS based clients.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Ensure that annotation enabled traffic-agents are uninstall when uninstalling the traffic-manager.</Title>\n\t<Body>\nA traffic-agent injected because the workload had the inject annotation enabled would sometimes not get uninstalled when the traffic-manager was uninstalled.\n  </Body>\n</Note>\n## Version 2.21.2 <span style={{fontSize:'16px'}}>(January 26)</span>\n<Note>\n\t<Title type=\"bugfix\">Fix panic when agentpf.client creates a Tunnel</Title>\n\t<Body>\nA race could occur where several requests where made to `agentpf.client.Tunnel` on a client that had errored when creating its port-forward to the agent. The implementation could handle one such requests but not several, resulting in a panic in situations where multiple simultaneous requests were made to the same client during a very short time period,\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix goroutine leak in dialer.</Title>\n\t<Body>\nThe context passed to the `Tunnel` call that creates a stream for a dialer, was not cancelled when the dialer was finished, so the stream was never properly closed, leading to one dormant goroutine for each stream.\n  </Body>\n</Note>\n## Version 2.21.1 <span style={{fontSize:'16px'}}>(December 17)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3741\">Allow ingest of serverless deployments without specifying an inject-container-ports annotation</Title>\n\t<Body>\nThe ability to intercept a workload without a service is built around the `telepresence.getambassador.io/inject-container-ports` annotation, and it was also required in order to ingest such a workload. This was counterintuitive and the requirement was removed. An ingest doesn't use a port.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Upgrade module dependencies to get rid of critical vulnerability.</Title>\n\t<Body>\nUpgrade module dependencies to latest available stable. This includes upgrading golang.org/x/crypto, which had critical issues, from 0.30.0 to 0.31.0 where those issues are resolved.\n  </Body>\n</Note>\n## Version 2.21.0 <span style={{fontSize:'16px'}}>(December 13)</span>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/vpn\">Automatic VPN conflict avoidance</Title>\n\t<Body>\nTelepresence not only detects subnet conflicts between the cluster and workstation VPNs but also resolves them by performing network address translation to move conflicting subnets out of the way.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/vpn\">Virtual Address Translation (VNAT).</Title>\n\t<Body>\nIt is now possible to use a virtual subnet without routing the affected IPs to a specific workload. A new `telepresence connect --vnat CIDR` flag was added that will perform virtual network address translation of cluster IPs. This flag is very similar to the `--proxy-via CIDR=WORKLOAD` introduced in 2.19, but without the need to specify a workload.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/engagements/container\">Intercepts targeting a specific container</Title>\n\t<Body>\nIn certain scenarios, the container owning the intercepted port differs from the container the intercept targets. This port owner's sole purpose is to route traffic from the service to the intended container, often using a direct localhost connection.\nThis update introduces a `--container <name>` option to the intercept command. While this option doesn't influence the port selection, it guarantees that the environment variables and mounts propagated to the client originate from the specified container. Additionally, if the `--replace` option is used, it ensures that this container is replaced.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"howtos/intercepts#ingest-your-service\">New telepresence ingest command</Title>\n\t<Body>\nThe new `telepresence ingest` command, similar to `telepresence intercept`, provides local access to the volume mounts and environment variables of a targeted container. However, unlike `telepresence intercept`, `telepresence ingest` does not redirect traffic to the container and ensures that the mounted volumes are read-only.\nAn ingest requires a traffic-agent to be installed in the pods of the targeted workload. Beyond that, it's a client-side operation. This allows developers to have multiple simultaneous ingests on the same container.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/docker-run#the-telepresence-curl-command\">New telepresence curl command</Title>\n\t<Body>\nThe new `telepresence curl` command runs curl from within a container. The command requires that a connection has been established using `telepresence connect --docker`, and the container that runs `curl` will share the same network as the containerized telepresence daemon.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/docker-run#the-telepresence-docker-run-command\">New telepresence docker-run command</Title>\n\t<Body>\nThe new `telepresence docker-run <flags and arguments>` requires that a connection has been established using `telepresence connect --docker` It will perform a `docker run <flags and arguments>` and add the flag necessary to ensure that started container shares the same network as the containerized telepresence daemon.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Mount everything read-only during intercept</Title>\n\t<Body>\nIt is now possible to append \":ro\" to the intercept `--mount` flag value. This ensures that all remote volumes that the intercept mounts are read-only.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/config\">Unify client configuration</Title>\n\t<Body>\nPreviously, client configuration was divided between the config.yml file and a Kubernetes extension. DNS and routing settings were initially found only in the extension. However, the Helm client structure allowed entries from both.\nTo simplify this, we've now aligned the config.yml and Kubernetes extension with the Helm client structure. This means DNS and routing settings are now included in both. The Kubernetes extension takes precedence over the config.yml and Helm client object.\nWhile the old-style Kubernetes extension is still supported for compatibility, it cannot be used with the new style.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Use WebSockets for port-forward instead of the now deprecated SPDY.</Title>\n\t<Body>\nTelepresence will now use WebSockets instead of SPDY when creating port-forwards to the Kubernetes Cluster, and will fall back to SPDY when connecting to clusters that don't support SPDY. Use of the deprecated SPDY can be forced by setting `cluster.forceSPDY=true` in the `config.yml`.\nSee [Streaming Transitions from SPDY to WebSockets](https://kubernetes.io/blog/2024/08/20/websockets-transition/) for more information about this transition.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Make usage data collection configurable using an extension point, and default to no-ops</Title>\n\t<Body>\nThe OSS code-base will no longer report usage data to the proprietary collector at Ambassador Labs. The actual calls to the collector remain, but will be no-ops unless a proper collector client is installed using an extension point.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/engagements/sidecar#disable-workloads\">Add deployments, statefulSets, replicaSets to workloads Helm chart value</Title>\n\t<Body>\nThe Helm chart value `workloads` now supports the kinds `deployments.enabled`, `statefulSets.enabled`, `replicaSets.enabled`. and `rollouts.enabled`. All except `rollouts` are enabled by default. The traffic-manager will ignore workloads, and Telepresence will not be able to intercept them, if the `enabled` of the corresponding kind is set to `false`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Improved command auto-completion</Title>\n\t<Body>\nThe auto-completion of namespaces, services, and containers have been added where appropriate, and the default file auto completion has been removed from most commands.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"reference/docker-run#the-telepresence-docker-run-command\">Docker run flags --publish, --expose, and --network now work with docker mode connections</Title>\n\t<Body>\nAfter establishing a connection to a cluster using `telepresence connect --docker`, you can run new containers that share the same network as the containerized daemon that maintains the connection. This enables seamless communication between your local development environment and the remote services.\nNormally, Docker has a limitation that prevents combining a shared network configuration with custom networks and exposing ports. However, Telepresence now elegantly circumvents this limitation so that a container started with `telepresence docker-run`, `telepresence intercept --docker-run`, or `telepresence ingest --docker-run` can use flags like `--network`, `--publish`, or `--expose`.\nTo achieve this, Telepresence temporarily adds the necessary network to the containerized daemon. This allows the new container to join the same network. Additionally, Telepresence starts extra socat containers to handle port mapping, ensuring that the desired ports are exposed to the local environment.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"howtos/cluster-in-vm\">Prevent recursion in the Telepresence Virtual Network Interface (VIF)</Title>\n\t<Body>\nNetwork problems may arise when running Kubernetes locally (e.g., Docker Desktop, Kind, Minikube, k3s), because the VIF on the host is also accessible from the cluster's nodes. A request that isn't handled by a cluster resource might be routed back into the VIF and cause a recursion.\nThese recursions can now be prevented by setting the client configuration property `routing.recursionBlockDuration` so that new connection attempts are temporarily blocked for a specific IP:PORT pair immediately after an initial attempt, thereby effectively ending the recursion.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Allow Helm chart to be included as a sub-chart</Title>\n\t<Body>\nThe Helm chart previously had the unnecessary restriction that the .Release.Name under which telepresence is installed is literally called \"traffic-manager\".  This restriction was preventing telepresence from being included as a sub-chart in a parent chart called anything but \"traffic-manager\". This restriction has been lifted.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add Windows arm64 client build</Title>\n\t<Body>\nTelepresence client is now available for Windows ARM64. Updated the release workflow files in github actions to build and publish the Windows ARM64 client.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">The --agents flag to telepresence uninstall is now the default.</Title>\n\t<Body>\nThe `telepresence uninstall` was once capable of uninstalling the traffic-manager as well as traffic-agents. This behavior has been deprecated for some time now and in this release, the command is all about uninstalling the agents. Therefore the `--agents` flag was made redundant and whatever arguments that are given to the command must be name of workloads that have an agent installed unless the `--all-agents` is used, in which case no arguments are allowed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Performance improvement for the telepresence list command</Title>\n\t<Body>\nThe `telepresence list` command will now retrieve its data from the traffic-manager, which significantly improves its performance when used on namespaces that have a lot of workloads.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">During an intercept, the local port defaults to the targeted port of the intercepted container instead of 8080.</Title>\n\t<Body>\nTelepresence mimics the environment of a target container during an intercept, so it's only natural that the default for the local port is determined by the targeted container port rather than just defaulting to 8080.\nA default can still be explicitly defined using the `config.intercept.defaultPort` setting.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Move the telepresence-intercept-env configmap data into traffic-manager configmap.</Title>\n\t<Body>\nThere's no need for two configmaps that store configuration data for the traffic manager. The traffic-manager configmap is also watched, so consolidating the configuration there saves some k8s API calls.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Tracing was removed.</Title>\n\t<Body>\nThe ability to collect trace has been removed along with the `telepresence gather-traces` and `telepresence upload-traces` commands. The underlying code was complex and has not been well maintained since its inception in 2022. We have received no feedback on it and seen no indication that it has ever been used.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Remove obsolete code checking the Docker Bridge for DNS</Title>\n\t<Body>\nThe DNS resolver checked the Docker bridge for messages on Linux. This code was obsolete and caused problems when running in Codespaces.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix telepresence connect confusion caused by /.dockerenv file</Title>\n\t<Body>\nA `/.dockerenv` will be present when running in a GitHub Codespaces environment. That doesn't mean that telepresence cannot use docker, or that the root daemon shouldn't start.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Cap timeouts.connectivityCheck at 5 seconds.</Title>\n\t<Body>\nThe timeout value of `timeouts.connectivityCheck` is used when checking if a cluster is already reachable without Telepresence setting up an additional network route. If it is, this timeout should be high enough to cover the delay when establishing a connection. If this delay is higher than a second, then chances are very low that the cluster already is reachable, and if it can, that all accesses to it will be very slow. In such cases, Telepresence will create its own network interface and do perform its own tunneling.\nThe default timeout for the check remains at 500 millisecond, which is more than sufficient for the majority of cases.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Prevent that traffic-manager injects a traffic-agent into itself.</Title>\n\t<Body>\nThe traffic-manager can never be a subject for an intercept, ingest, or proxy-via, because that means that it injects the traffic-agent into itself, and it is not designed to do that. A user attempting this will now see a meaningful error message.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Don't include pods in the kube-system namespace when computing pod-subnets from pod IPs</Title>\n\t<Body>\nA user would normally never access pods in the `kube-system` namespace directly, and automatically including pods included there when computing the subnets will often lead to problems when running the cluster locally. This namespace is therefore now excluded in situations when the pod subnets are computed from the IPs of pods. Services in this namespace will still be available through the service subnet.\nIf a user should require the pod-subnet to be mapped, it can be added to the `client.routing.alsoProxy` list in the helm chart.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Let routes belonging to an allowed conflict be added as a static route on Linux.</Title>\n\t<Body>\nThe `allowConflicting` setting didn't always work on Linux because the conflicting subnet was just added as a link to the TUN device, and therefore didn't get subjected to routing rule used to assign priority to the given subnet.\n  </Body>\n</Note>\n## Version 2.20.3 <span style={{fontSize:'16px'}}>(November 18)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3722\">Ensure that Telepresence works with GitHub Codespaces</Title>\n\t<Body>\nGitHub Codespaces runs in a container, but not as root. Telepresence didn't handle this situation correctly and only started the user daemon. The root daemon was never started.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3715\">Mounts not working correctly when connected with --proxy-via</Title>\n\t<Body>\nA mount would try to connect to the sftp/ftp server using the original (cluster side) IP although that IP was translated into a virtual IP when using `--proxy-via`.\n  </Body>\n</Note>\n## Version 2.20.2 <span style={{fontSize:'16px'}}>(October 21)</span>\n<Note>\n\t<Title type=\"bugfix\">Crash in traffic-manager configured with agentInjector.enabled=false</Title>\n\t<Body>\nA traffic-manager that was installed with the Helm value `agentInjector.enabled=false` crashed when a client used the commands `telepresence version` or `telepresence status`. Those commands would call a method on the traffic-manager that panicked if no traffic-agent was present. This method will now instead return the standard `Unavailable` error code, which is expected by the caller.\n  </Body>\n</Note>\n## Version 2.20.1 <span style={{fontSize:'16px'}}>(October 10)</span>\n<Note>\n\t<Title type=\"bugfix\">Some workloads missing in the telepresence list output (typically replicasets owned by rollouts).</Title>\n\t<Body>\nVersion 2.20.0 introduced a regression in the `telepresence list` command, resulting in the omission of all workloads that were owned by another workload. The correct behavior is to just omit those workloads that are owned by the supported workload kinds `Deployment`, `ReplicaSet`, `StatefulSet`, and `Rollout`. Furthermore, the `Rollout` kind must only be considered supported when the Argo Rollouts feature is enabled in the traffic-manager.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Allow comma separated list of daemons for the gather-logs command.</Title>\n\t<Body>\nThe name of the `telepresence gather-logs` flag `--daemons` suggests that the argument can contain more than one daemon, but prior to this fix, it couldn't. It is now possible to use a comma separated list, e.g. `telepresence gather-logs --daemons root,user`.\n  </Body>\n</Note>\n## Version 2.20.0 <span style={{fontSize:'16px'}}>(October  3)</span>\n<Note>\n\t<Title type=\"feature\">Add timestamp to telepresence_logs.zip filename.</Title>\n\t<Body>\nTelepresence is now capable of easily find telepresence gather-logs by certain timestamp.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"https://telepresence.io/docs/reference/engagements/cli#intercepting-without-a-service\">Enable intercepts of workloads that have no service.</Title>\n\t<Body>\nTelepresence is now capable of intercepting workloads that have no associated service. The intercept will then target container port instead of a service port. The new behavior is enabled by adding a <code>telepresence.getambassador.io/inject-container-ports</code> annotation where the value is a comma separated list of port identifiers consisting of either the name or the port number of a container port, optionally suffixed with `/TCP` or `/UDP`.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss\">Publish the OSS version of the telepresence Helm chart</Title>\n\t<Body>\nThe OSS version of the telepresence helm chart is now available at ghcr.io/telepresenceio/telepresence-oss, and can be installed using the command:<br/> <code>helm install traffic-manager oci://ghcr.io/telepresenceio/telepresence-oss --namespace ambassador --version 2.20.0</code> The chart documentation is published at <a href=\"https://artifacthub.io/packages/helm/telepresence-oss/telepresence-oss\">ArtifactHUB</a>.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"https://telepresence.io/docs/reference/environment\">Control the syntax of the environment file created with the intercept flag --env-file</Title>\n\t<Body>\nA new <code>--env-syntax &lt;syntax&gt;</code> was introduced to allow control over the syntax of the file created when using the intercept flag <code>--env-file &lt;file&gt;</code>. Valid syntaxes are &quot;docker&quot;, &quot;compose&quot;, &quot;sh&quot;, &quot;csh&quot;, &quot;cmd&quot;, and &quot;ps&quot;; where &quot;sh&quot;, &quot;csh&quot;, and &quot;ps&quot; can be suffixed with &quot;:export&quot;.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add support for Argo Rollout workloads.</Title>\n\t<Body>\nTelepresence now has an opt-in support for Argo Rollout workloads. The behavior is controlled by `workloads.argoRollouts.enabled` Helm chart value. It is recommended to set the following annotation <code>telepresence.getambassador.io/inject-traffic-agent: enabled</code> to avoid creation of unwanted revisions.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Enable intercepts of containers that bind to podIP</Title>\n\t<Body>\nIn previous versions, the traffic-agent would route traffic to localhost during periods when an intercept wasn't active. This made it impossible for an application to bind to the pod's IP, and it also meant that service meshes binding to the podIP would get bypassed, both during and after an intercept had been made. This is now changed, so that the traffic-agent instead forwards non intercepted requests to the pod's IP, thereby enabling the application to either bind to localhost or to that IP.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Use ghcr.io/telepresenceio instead of docker.io/datawire for OSS images and the telemount Docker volume plugin.</Title>\n\t<Body>\nAll OSS telepresence images and the telemount Docker plugin are now published at the public registry ghcr.io/telepresenceio and all references from the client and traffic-manager has been updated to use this registry instead of the one at docker.io/datawire.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Use nftables instead of iptables-legacy</Title>\n\t<Body>\nSome time ago, we introduced iptables-legacy because users had problems using Telepresence with Fly.io where nftables wasn't supported by the kernel. Fly.io has since fixed this, so Telepresence will now use nftables again. This in turn, ensures that modern systems that lack support iptables-legacy will work.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Root daemon wouldn't start when sudo timeout was zero.</Title>\n\t<Body>\nThe root daemon refused to start when <code>sudo</code> was configured with a <code>timestamp_timeout=0</code>. This was due to logic that first requested root privileges using a sudo call, and then relied on that these privileges were cached, so that a subsequent call using <code>--non-interactive</code> was guaranteed to succeed. This logic will now instead do one single sudo call, and rely solely on sudo to print an informative prompt and start the daemon in the background.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Detect minikube network when connecting with --docker</Title>\n\t<Body>\nA <code>telepresence connect --docker</code> failed when attempting to connect to a minikube that uses a docker driver because the containerized daemon did not have access to the <code>minikube</code> docker network. Telepresence will now detect an attempt to connect to that network and attach it to the daemon container as needed.\n  </Body>\n</Note>\n## Version 2.19.1 <span style={{fontSize:'16px'}}>(July 12)</span>\n<Note>\n\t<Title type=\"feature\" docs=\"https://github.com/telepresenceio/telepresence/issues/3609\">Add brew support for the OSS version of Telepresence.</Title>\n\t<Body>\nThe Open-Source Software version of Telepresence can now be installed using the brew formula via <code>brew install telepresenceio/telepresence/telepresence-oss</code>.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add --create-namespace flag to the telepresence helm install command.</Title>\n\t<Body>\nA <code>--create-namespace</code> (default <code>true</code>) flag was added to the <code>telepresence helm install</code> command. No attempt will be made to create a namespace for the traffic-manager if it is explicitly set to <code>false</code>. The command will then fail if the namespace is missing.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Introduce DNS fallback on Windows.</Title>\n\t<Body>\nA <code>network.defaultDNSWithFallback</code> config option has been introduced on Windows. It will cause the DNS-resolver to fall back to the resolver that was first in the list prior to when Telepresence establishes a connection. The option is default <code>true</code> since it is believed to give the best experience but can be set to <code>false</code> to restore the old behavior.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"https://github.com/datawire/homebrew-blackbird/issues/19\">Brew now supports MacOS (amd64/arm64) / Linux (amd64)</Title>\n\t<Body>\nThe brew formula can now dynamically support MacOS (amd64/arm64) / Linux (amd64) in a single formula\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to provide an externally-provisioned webhook secret</Title>\n\t<Body>\nAdded <code>supplied</code> as a new option for <code>agentInjector.certificate.method</code>. This fully disables the generation of the Mutating Webhook's secret, allowing the chart to use the values of a pre-existing secret named <code>agentInjector.secret.name</code>. Previously, the install would fail when it attempted to create or update the externally-managed secret.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Let PTR query for DNS server return the cluster domain.</Title>\n\t<Body>\nThe <code>nslookup</code> program on Windows uses a PTR query to retrieve its displayed \"Server\" property. This Telepresence DNS resolver will now return the cluster domain on such a query.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add scheduler name to PODs templates.</Title>\n\t<Body>\nA new Helm chart value <code>schedulerName</code> has been added. With this feature, we are able to define some particular schedulers from Kubernetes to apply some different strategies to allocate telepresence resources, including the Traffic Manager and hooks pods.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Race in traffic-agent injector when using inject annotation</Title>\n\t<Body>\nApplying multiple deployments that used the <code>telepresence.getambassador.io/inject-traffic-agent: enabled</code> would cause a race condition, resulting in a large number of created pods that eventually had to be deleted, or sometimes in pods that didn't contain a traffic agent.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix configuring custom agent security context</Title>\n\t<Body>\n-> The traffic-manager helm chart will now correctly use a custom agent security context if one is provided.\n  </Body>\n</Note>\n## Version 2.19.0 <span style={{fontSize:'16px'}}>(June 15)</span>\n<Note>\n\t<Title type=\"feature\">Warn when an Open Source Client connects to an Enterprise Traffic Manager.</Title>\n\t<Body>\nThe difference between the OSS and the Enterprise offering is not well understood, and OSS users often install a traffic-manager using the Helm chart published at getambassador.io. This Helm chart installs an enterprise traffic-manager, which is probably not what the user would expect. Telepresence will now warn when an OSS client connects to an enterprise traffic-manager and suggest switching to an enterprise client, or use <code>telepresence helm install</code> to install an OSS traffic-manager.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add scheduler name to PODs templates.</Title>\n\t<Body>\nA new Helm chart value <code>schedulerName</code> has been added. With this feature, we are able to define some particular schedulers from Kubernetes to apply some different strategies to allocate telepresence resources, including the Traffic Manager and hooks pods.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Improve traffic-manager performance in very large clusters.</Title>\n\t<Body>\n-> The traffic-manager will now use a shared-informer when keeping track of deployments. This will significantly reduce the load on the Kublet in large clusters and therefore lessen the risk for the traffic-manager being throttled, which can lead to other problems.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Kubeconfig exec authentication failure when connecting with --docker from a WSL linux host</Title>\n\t<Body>\nClusters like Amazon EKS often use a special authentication binary that is declared in the kubeconfig using an <code>exec</code> authentication strategy. This binary is normally not available inside a container. Consequently, a modified kubeconfig is used when <code>telepresence connect --docker</code> executes, appointing a <code>kubeauth </code> binary which instead retrieves the authentication from a port on the Docker host that communicates with another process outside of Docker. This process then executes the original <code>exec</code> command to retrieve the necessary credentials.\nThis setup was problematic when using WSL, because even though <code>telepresence connect --docker</code> was executed on a Linux host, the Docker host available from <code>host.docker.internal</code> that the <code>kubeauth</code> connected to was the Windows host running Docker Desktop. The fix for this was to use the local IP of the default route instead of <code>host.docker.internal</code> when running under WSL..\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix bug in workload cache, causing endless recursion when a workload uses the same name as its owner.</Title>\n\t<Body>\nThe workload cache was keyed by name and namespace, but not by kind, so a workload named the same as its owner workload would be found using the same key. This led to the workload finding itself when looking up its owner, which in turn resulted in an endless recursion when searching for the topmost owner.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">FailedScheduling events mentioning node availability considered fatal when waiting for agent to arrive.</Title>\n\t<Body>\nThe traffic-manager considers some events as fatal when waiting for a traffic-agent to arrive after an injection has been initiated. This logic would trigger on events like &quot;Warning FailedScheduling 0/63 nodes are available&quot; although those events indicate a recoverable condition and kill the wait. This is now fixed so that the events are logged but the wait continues.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Improve how the traffic-manager resolves DNS when no agent is installed.</Title>\n\t<Body>\nThe traffic-manager is typically installed into a namespace different from the one that clients are connected to. It's therefore important that the traffic-manager adds the client's namespace when resolving single label names in situations where there are any agents to dispatch the DNS query to.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Removal of ability import legacy artifact into Helm.</Title>\n\t<Body>\nA helm install would make attempts to find manually installed artifacts and make them managed by Helm by adding the necessary labels and annotations. This was important when the Helm chart was first introduced but is far less so today, and this legacy import was therefore removed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://docs.docker.com/engine/deprecated/#container-short-id-in-network-aliases-field\">Docker aliases deprecation caused failure to detect Kind cluster.</Title>\n\t<Body>\nThe logic for detecting if a cluster is a local Kind cluster, and therefore needs some special attention when using <code>telepresence connect --docker</code>, relied on the presence of <code>Aliases</code> in the Docker network that a Kind cluster sets up. In Docker versions from 26 and up, this value is no longer used, but the corresponding info can instead be found in the new <code>DNSNames</code> field.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/2814\">Include svc as a top-level domain in the DNS resolver.</Title>\n\t<Body>\nIt's not uncommon that use-cases involving Kafka or other middleware use FQNs that end with &quot;svc&quot;. The core-DNS resolver in Kubernetes can resolve such names. With this bugfix, the Telepresence DNS resolver will also be able to resolve them, and thereby remove the need to add &quot;.svc&quot; to the include-suffix list.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to enable/disable the mutating webhook.</Title>\n\t<Body>\nA new Helm chart boolean value <code>agentInjector.enable</code> has been added that controls the agent-injector service and its associated mutating webhook. If set to <code>false</code>, the service, the webhook, and the secrets and certificates associated with it, will no longer be installed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to mount a webhook secret.</Title>\n\t<Body>\nA new Helm chart value <code>agentInjector.certificate.accessMethod</code> which can be set to <code>watch</code> (the default) or <code>mount</code> has been added. The <code>mount</code> setting is intended for clusters with policies that prevent containers from doing a <code>get</code>, <code>list</code> or <code>watch</code> of a <code>Secret</code>, but where a latency of up to 90 seconds is acceptable between the time the secret is regenerated and the agent-injector picks it up.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Make it possible to specify ignored volume mounts using path prefix.</Title>\n\t<Body>\nVolume mounts like <code>/var/run/secrets/kubernetes.io</code> are not declared in the workload. Instead, they are injected during pod-creation and their names are generated. It is now possible to ignore such mounts using a matching path prefix.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Make the telemount Docker Volume plugin configurable</Title>\n\t<Body>\nA <code>telemount</code> object was added to the <code>intercept</code> object in <code>config.yml</code> (or Helm value <code>client.intercept</code>), so that the automatic download and installation of this plugin can be fully customised.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add option to load the kubeconfig yaml from stdin during connect.</Title>\n\t<Body>\nThis allows another process with a kubeconfig already loaded in memory to directly pass it to <code>telepresence connect</code> without needing a separate file. Simply use a dash \"-\" as the filename for the <code>--kubeconfig</code> flag.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add ability to specify agent security context.</Title>\n\t<Body>\nA new Helm chart value <code>agent.securityContext</code> that will allow configuring the security context of the injected traffic agent.  The value can be set to a valid Kubernetes securityContext object, or can be set to an empty value (<code>{}</code>) to ensure the agent has no defined security context.  If no value is specified, the traffic manager will set the agent's security context to the same as the first container's of the workload being injected into.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Tracing is no longer enabled by default.</Title>\n\t<Body>\nTracing must now be enabled explicitly in order to use the <code>telepresence gather-traces</code> command.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Removal of timeouts that are no longer in use</Title>\n\t<Body>\nThe <code>config.yml</code> values <code>timeouts.agentInstall</code> and <code>timeouts.apply</code> haven't been in use since versions prior to 2.6.0, when the client was responsible for installing the traffic-agent. These timeouts are now removed from the code-base, and a warning will be printed when attempts are made to use them.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Search all private subnets to find one open for dnsServerSubnet</Title>\n\t<Body>\nThis resolves a bug that did not test all subnets in a private range, sometimes resulting in the warning, \"DNS doesn't seem to work properly.\"\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Docker aliases deprecation caused failure to detect Kind cluster.</Title>\n\t<Body>\nThe logic for detecting if a cluster is a local Kind cluster, and therefore needs some special attention when using <code>telepresence connect --docker</code>, relied on the presence of <code>Aliases</code> in the Docker network that a Kind cluster sets up. In Docker versions from 26 and up, this value is no longer used, but the corresponding info can instead be found in the new <code>DNSNames</code> field.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Creation of individual pods was blocked by the agent-injector webhook.</Title>\n\t<Body>\nAn attempt to create a pod was blocked unless it was provided by a workload. Hence, commands like <code>kubectl run -i busybox --rm --image=curlimages/curl --restart=Never -- curl echo-easy.default</code> would be blocked from executing.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix panic due to root daemon not running.</Title>\n\t<Body>\nIf a <code>telepresence connect</code> was made at a time when the root daemon was not running (an abnormal condition) and a subsequent intercept was then made, a panic would occur when the port-forward to the agent was set up. This is now fixed so that the initial <code>telepresence connect</code> is refused unless the root daemon is running.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Get rid of telemount plugin stickiness</Title>\n\t<Body>\nThe <code>datawire/telemount</code> that is automatically downloaded and installed, would never be updated once the installation was made. Telepresence will now check for the latest release of the plugin and cache the result of that check for 24 hours. If a new version arrives, it will be installed and used.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Use route instead of address for CIDRs with masks that don't allow \"via\"</Title>\n\t<Body>\nA CIDR with a mask that leaves less than two bits (/31 or /32 for IPv4) cannot be added as an address to the VIF, because such addresses must have bits allowing a \"via\" IP.\nThe logic was modified to allow such CIDRs to become static routes, using the VIF base address as their \"via\", rather than being VIF addresses in their own right.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Containerized daemon created cache files owned by root</Title>\n\t<Body>\nWhen using <code>telepresence connect --docker</code> to create a containerized daemon, that daemon would sometimes create files in the cache that were owned by root, which then caused problems when connecting without the <code>--docker</code> flag.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Remove large number of requests when traffic-manager is used in large clusters.</Title>\n\t<Body>\nThe traffic-manager would make a very large number of API requests during cluster start-up or when many services were changed for other reasons. The logic that did this was refactored and the number of queries were significantly reduced.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Don't patch probes on replaced containers.</Title>\n\t<Body>\nA container that is being replaced by a <code>telepresence intercept --replace</code> invocation will have no liveness-, readiness, nor startup-probes. Telepresence didn't take this into consideration when injecting the traffic-agent, but now it will refrain from patching symbolic port names of those probes.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Don't rely on context name when deciding if a kind cluster is used.</Title>\n\t<Body>\nThe code that auto-patches the kubeconfig when connecting to a kind cluster from within a docker container, relied on the context name starting with \"kind-\", but although all contexts created by kind have that name, the user is still free to rename it or to create other contexts using the same connection properties. The logic was therefore changed to instead look for a loopback service address.\n  </Body>\n</Note>\n## Version 2.18.0 <span style={{fontSize:'16px'}}>(February  9)</span>\n<Note>\n\t<Title type=\"feature\">Include the image for the traffic-agent in the output of the version and status commands.</Title>\n\t<Body>\nThe version and status commands will now output the image that the traffic-agent will be using when injected by the agent-injector.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Custom DNS using the client DNS resolver.</Title>\n\t<Body>\n<p>A new <code>telepresence connect --proxy-via CIDR=WORKLOAD</code> flag was introduced, allowing Telepresence to translate DNS responses matching specific subnets into virtual IPs that are used locally. Those virtual IPs are then routed (with reverse translation) via the pod's of a given workload. This makes it possible to handle custom DNS servers that resolve domains into loopback IPs. The flag may also be used in cases where the cluster's subnets are in conflict with the workstation's VPN.</p> <p>The CIDR can also be a symbolic name that identifies a subnet or list of subnets:</p><table><tbody> <tr><td><code>also</code></td><td>All subnets added with --also-proxy</td></tr> <tr><td><code>service</code></td><td>The cluster's service subnet</td></tr> <tr><td><code>pods</code></td><td>The cluster's pod subnets.</td></tr> <tr><td><code>all</code></td><td>All of the above.</td></tr> </tbody></table>\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Ensure that agent.appProtocolStrategy is propagated correctly.</Title>\n\t<Body>\nThe <code>agent.appProtocolStrategy</code> was inadvertently dropped when moving license related code fromm the OSS repository the repository for the Enterprise version of Telepresence. It has now been restored.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Include non-default zero values in output of telepresence config view.</Title>\n\t<Body>\nThe <code>telepresence config view</code> command will now print zero values in the output when the default for the value is non-zero.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Restore ability to run the telepresence CLI in a docker container.</Title>\n\t<Body>\nThe improvements made to be able to run the telepresence daemon in docker using <code>telepresence connect --docker</code> made it impossible to run both the CLI and the daemon in docker. This commit fixes that and also ensures that the user- and root-daemons are merged in this scenario when the container runs as root.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Remote mounts when intercepting with the --replace flag.</Title>\n\t<Body>\nA <code>telepresence intercept --replace</code> did not correctly mount all volumes, because when the intercepted container was removed, its mounts were no longer visible to the agent-injector when it was subjected to a second invocation. The container is now kept in place, but with an image that just sleeps infinitely.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Intercepting with the --replace flag will no longer require all subsequent intercepts to use --replace.</Title>\n\t<Body>\nA <code>telepresence intercept --replace</code> will no longer switch the mode of the intercepted workload, forcing all subsequent intercepts on that workload to use <code>--replace</code> until the agent is uninstalled. Instead, <code>--replace</code> can be used interchangeably just like any other intercept flag.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Kubeconfig exec authentication with context names containing colon didn't work on Windows</Title>\n\t<Body>\nThe logic added to allow the root daemon to connect directly to the cluster using the user daemon as a proxy for exec type authentication in the kube-config, didn't take into account that a context name sometimes contains the colon \":\" character. That character cannot be used in filenames on windows because it is the drive letter separator.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Provide agent name and tag as separate values in Helm chart</Title>\n\t<Body>\nThe <code>AGENT_IMAGE</code> was a concatenation of the agent's name and tag. This is now changed so that the env instead contains an <code>AGENT_IMAGE_NAME</code> and <code>AGENT_INAGE_TAG</code>. The <code>AGENT_IMAGE </code> is removed. Also, a new env <code>REGISTRY</code> is added, where the registry of the traffic- manager image is provided. The <code>AGENT_REGISTRY</code> is no longer required and will default to <code>REGISTRY</code> if not set.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Environment interpolation expressions were prefixed twice.</Title>\n\t<Body>\nTelepresence would sometimes prefix environment interpolation expressions in the traffic-agent twice so that an expression that looked like <code>$(SOME_NAME)</code> in the app-container, ended up as <code> $(_TEL_APP_A__TEL_APP_A_SOME_NAME)</code> in the corresponding expression in the traffic-agent.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Panic in root-daemon on darwin workstations with full access to cluster network.</Title>\n\t<Body>\nA darwin machine with full access to the cluster's subnets will never create a TUN-device, and a check was missing if the device actually existed, which caused a panic in the root daemon.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Show allow-conflicting-subnets in telepresence status and telepresence config view.</Title>\n\t<Body>\nThe <code>telepresence status</code> and <code>telepresence config view</code> commands didn't show the <code>allowConflictingSubnets</code> CIDRs because the value wasn't propagated correctly to the CLI.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">It is now possible use a host-based connection and containerized connections simultaneously.</Title>\n\t<Body>\nOnly one host-based connection can exist because that connection will alter the DNS to reflect the namespace of the connection. but it's now possible to create additional connections using <code>--docker</code> while retaining the host-based connection.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Ability to set the hostname of a containerized daemon.</Title>\n\t<Body>\nThe hostname of a containerized daemon defaults to be the container's ID in Docker. You now can override the hostname using <code>telepresence connect --docker --hostname &lt;a name&gt;</code>.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">New <code>--multi-daemon</code>flag to enforce a consistent structure for the status command output.</Title>\n\t<Body>\nThe output of the <code>telepresence status</code> when using <code>--output json</code> or <code>--output yaml</code> will either show an object where the <code>user_daemon</code> and <code>root_daemon</code> are top level elements, or when multiple connections are used, an object where a <code>connections</code> list contains objects with those daemons. The flag <code>--multi-daemon</code> will enforce the latter structure even when only one daemon is connected so that the output can be parsed consistently. The reason for keeping the former structure is to retain backward compatibility with existing parsers.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Make output from telepresence quit more consistent.</Title>\n\t<Body>\nA quit (without -s) just disconnects the host user and root daemons but will quit a container based daemon. The message printed was simplified to remove some have/has is/are errors caused by the difference.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix &quot;tls: bad certificate&quot; errors when refreshing the mutator-webhook secret</Title>\n\t<Body>\nThe <code>agent-injector</code> service will now refresh the secret used by the <code>mutator-webhook</code> each time a new connection is established, thus preventing the certificates to go out-of-sync when the secret is regenerated.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Keep telepresence-agents configmap in sync with pod states.</Title>\n\t<Body>\nAn intercept attempt that resulted in a timeout due to failure of injecting the traffic-agent left the <code>telepresence-agents</code> configmap in a state that indicated that an agent had been added, which caused problems for subsequent intercepts after the problem causing the first failure had been fixed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">The <code>telepresence status</code> command will now report the status of all running daemons.</Title>\n\t<Body>\nA <code>telepresence status</code>, issued when multiple containerized daemons were active, would error with &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now fixed so that the command instead reports the status of all running daemons.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">The <code>telepresence version</code> command will now report the version of all running daemons.</Title>\n\t<Body>\nA <code>telepresence version</code>, issued when multiple containerized daemons were active, would error with &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now fixed so that the command instead reports the version of all running daemons.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Multiple containerized daemons can now be disconnected using <code>telepresence quit -s</code></Title>\n\t<Body>\nA <code>telepresence quit -s</code>, issued when multiple containerized daemons were active, would error with &quot;multiple daemons are running, please select one using the --use &lt;match&gt; flag&quot;. This is now fixed so that the command instead quits all daemons.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">The DNS search path on Windows is now restored when Telepresence quits</Title>\n\t<Body>\nThe DNS search path that Telepresence uses to simulate the DNS lookup functionality in the connected cluster namespace was not removed by a <code>telepresence quit</code>, resulting in connectivity problems from the workstation. Telepresence will now remove the entries that it has added to the search list when it quits.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">The user-daemon would sometimes get killed when used by multiple simultaneous CLI clients.</Title>\n\t<Body>\nThe user-daemon would die with a fatal &quot;fatal error: concurrent map writes&quot; error in the <code>connector.log</code>, effectively killing the ongoing connection.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Multiple services ports using the same target port would not get intercepted correctly.</Title>\n\t<Body>\nIntercepts didn't work when multiple service ports were using the same container port. Telepresence would think that one of the ports wasn't intercepted and therefore disable the intercept of the container port.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Root daemon refuses to disconnect.</Title>\n\t<Body>\nThe root daemon would sometimes hang forever when attempting to disconnect due to a deadlock in the VIF-device.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix panic in user daemon when traffic-manager was unreachable</Title>\n\t<Body>\nThe user daemon would panic if the traffic-manager was unreachable. It will now instead report a proper error to the client.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Removal of backward support for versions predating 2.6.0</Title>\n\t<Body>\nThe telepresence helm installer will no longer discover and convert workloads that were modified by versions prior to 2.6.0. The traffic manager will and no longer support the muxed tunnels used in versions prior to 2.5.0.\n  </Body>\n</Note>\n## Version 2.17.0 <span style={{fontSize:'16px'}}>(November 14)</span>\n<Note>\n\t<Title type=\"feature\">Additional Prometheus metrics to track intercept/connect activity</Title>\n\t<Body>\nThis feature adds the following metrics to the Prometheus endpoint: <code>connect_count</code>, <code>connect_active_status</code>, <code>intercept_count</code>, and <code>intercept_active_status</code>. These are labeled by client/install_id. Additionally, the <code>intercept_count</code> metric has been renamed to <code>active_intercept_count</code> for clarity.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Make the Telepresence client docker image configurable.</Title>\n\t<Body>\nThe docker image used when running a Telepresence intercept in docker mode can now be configured using the setting <code>images.clientImage</code> and will default first to the value of the environment <code> TELEPRESENCE_CLIENT_IMAGE</code>, and then to the value preset by the telepresence binary. This configuration setting is primarily intended for testing purposes.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Use traffic-agent port-forwards for outbound and intercepted traffic.</Title>\n\t<Body>\nThe telepresence TUN-device is now capable of establishing direct port-forwards to a traffic-agent in the connected namespace. That port-forward is then used for all outbound traffic to the device, and also for all traffic that arrives from intercepted workloads. Getting rid of the extra hop via the traffic-manager improves performance and reduces the load on the traffic-manager. The feature can only be used if the client has Kubernetes port-forward permissions to the connected namespace. It can be disabled by setting <code> cluster.agentPortForward</code> to <code>false</code> in <code>config.yml</code>.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Improve outbound traffic performance.</Title>\n\t<Body>\nThe root-daemon now communicates directly with the traffic-manager instead of routing all outbound traffic through the user-daemon. The root-daemon uses a patched kubeconfig where <code>exec</code> configurations to obtain credentials are dispatched to the user-daemon. This to ensure that all authentication plugins will execute in user-space. The old behavior of routing everything through the user-daemon can be restored by setting <code>cluster.connectFromRootDaemon</code> to <code>false</code> in <code>config.yml</code>.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">New networking CLI flag --allow-conflicting-subnets</Title>\n\t<Body>\ntelepresence connect (and other commands that kick off a connect) now accepts an --allow-conflicting-subnets CLI flag. This is equivalent to client.routing.allowConflictingSubnets in the helm chart, but can be specified at connect time. It will be appended to any configuration pushed from the traffic manager.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Warn if large version mismatch between traffic manager and client.</Title>\n\t<Body>\nPrint a warning if the minor version diff between the client and the traffic manager is greater than three.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">The authenticator binary was removed from the docker image.</Title>\n\t<Body>\nThe <code>authenticator</code> binary, used when serving proxied <code>exec</code> kubeconfig credential retrieval, has been removed. The functionality was instead added as a subcommand to the <code>telepresence </code> binary.\n  </Body>\n</Note>\n## Version 2.16.1 <span style={{fontSize:'16px'}}>(October 12)</span>\n<Note>\n\t<Title type=\"feature\">Add --docker-debug flag to the telepresence intercept command.</Title>\n\t<Body>\nThis flag is similar to <code>--docker-build</code> but will start the container with more relaxed security using the <code>docker run</code> flags <code>--security-opt apparmor=unconfined --cap-add SYS_PTRACE</code>.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add a --export option to the telepresence connect command.</Title>\n\t<Body>\nIn some situations it is necessary to make some ports available to the host from a containerized telepresence daemon. This commit adds a repeatable <code>--expose &lt;docker port exposure&gt;</code> flag to the connect command.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Prevent agent-injector webhook from selecting from kube-xxx namespaces.</Title>\n\t<Body>\nThe <code>kube-system</code> and <code>kube-node-lease</code> namespaces should not be affected by a global agent-injector webhook by default. A default <code>namespaceSelector</code> was therefore added to the Helm Chart <code>agentInjector.webhook</code> that contains a <code>NotIn</code> preventing those namespaces from being selected.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Backward compatibility for pod template TLS annotations.</Title>\n\t<Body>\nUsers of Telepresence &lt; 2.9.0 that make use of the pod template TLS annotations were unable to upgrade because the annotation names have changed (now prefixed by \"telepresence.\"), and the environment expansion of the annotation values was dropped. This fix restores support for the old names (while retaining the new ones) and the environment expansion.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"security\">Built with go 1.21.3</Title>\n\t<Body>\nBuilt Telepresence with go 1.21.3 to address CVEs.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Match service selector against pod template labels</Title>\n\t<Body>\nWhen listing intercepts (typically by calling <code>telepresence list</code>) selectors of services are matched against workloads. Previously the match was made against the labels of the workload, but now they are matched against the labels pod template of the workload. Since the service would actually be matched against pods this is more correct. The most common case when this makes a difference is that statefulsets now are listed when they should.\n  </Body>\n</Note>\n## Version 2.16.0 <span style={{fontSize:'16px'}}>(October  2)</span>\n<Note>\n\t<Title type=\"bugfix\">The helm sub-commands will no longer start the user daemon.</Title>\n\t<Body>\nThe <code>telepresence helm install/upgrade/uninstall</code> commands will no longer start the telepresence user daemon because there's no need to connect to the traffic-manager in order for them to execute.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Routing table race condition</Title>\n\t<Body>\nA race condition would sometimes occur when a Telepresence TUN device was deleted and another created in rapid succession that caused the routing table to reference interfaces that no longer existed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Stop lingering daemon container</Title>\n\t<Body>\nWhen using <code>telepresence connect --docker</code>, a lingering container could be present, causing errors like &quot;The container name NN is already in use by container XX ...&quot;. When this happens, the connect logic will now give the container some time to stop and then call <code>docker stop NN</code> to stop it before retrying to start it.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Add file locking to the Telepresence cache</Title>\n\t<Body>\nFiles in the Telepresence cache are accesses by multiple processes. The processes will now use advisory locks on the files to guarantee consistency.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Lock connection to namespace</Title>\n\t<Body>\nThe behavior changed so that a connected Telepresence client is bound to a namespace. The namespace can then not be changed unless the client disconnects and reconnects. A connection is also given a name. The default name is composed from <code>&lt;kube context name&gt;-&lt;namespace&gt;</code> but can be given explicitly when connecting using <code>--name</code>. The connection can optionally be identified using the option <code>--use &lt;name match&gt;</code> (only needed when docker is used and more than one connection is active).\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Deprecation of global --context and --docker flags.</Title>\n\t<Body>\nThe global flags <code>--context</code> and <code>--docker</code> will now be considered deprecated unless used with commands that accept the full set of Kubernetes flags (e.g. <code>telepresence connect</code>).\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Deprecation of the --namespace flag for the intercept command.</Title>\n\t<Body>\nThe <code>--namespace</code> flag is now deprecated for <code>telepresence intercept</code> command. The flag can instead be used with all commands that accept the full set of Kubernetes flags (e.g. <code>telepresence connect</code>).\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Legacy code predating version 2.6.0 was removed.</Title>\n\t<Body>\nThe telepresence code-base still contained a lot of code that would modify workloads instead of relying on the mutating webhook installer when a traffic-manager version predating version 2.6.0 was discovered. This code has now been removed.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add `telepresence list-namespaces` and `telepresence list-contexts` commands</Title>\n\t<Body>\nThese commands can be used to check accessible namespaces and for automation.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Implicit connect warning</Title>\n\t<Body>\nA deprecation warning will be printed if a command other than <code>telepresence connect</code> causes an implicit connect to happen. Implicit connects will be removed in a future release.\n  </Body>\n</Note>\n## Version 2.15.1 <span style={{fontSize:'16px'}}>(September  6)</span>\n<Note>\n\t<Title type=\"security\">Rebuild with go 1.21.1</Title>\n\t<Body>\nRebuild Telepresence with go 1.21.1 to address CVEs.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"security\">Set security context for traffic agent</Title>\n\t<Body>\nOpenshift users reported that the traffic agent injection was failing due to a missing security context.\n  </Body>\n</Note>\n## Version 2.15.0 <span style={{fontSize:'16px'}}>(August 29)</span>\n<Note>\n\t<Title type=\"security\">Add ASLR to telepresence binaries</Title>\n\t<Body>\nASLR hardens binary sercurity against fixed memory attacks.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\" docs=\"https://github.com/telepresenceio/telepresence/issues/3259\">Added client builds for arm64 architecture.</Title>\n\t<Body>\nUpdated the release workflow files in github actions to including building and publishing the client binaries for arm64 architecture.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/pull/3300\">KUBECONFIG env var can now be used with the docker mode.</Title>\n\t<Body>\nIf provided, the KUBECONFIG environment variable was passed to the kubeauth-foreground service as a parameter. However, since it didn't exist, the CLI was throwing an error when using <code>telepresence connect --docker</code>.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/pull/3298\">Fix deadlock while watching workloads</Title>\n\t<Body>\nThe <code>telepresence list --output json-stream</code> wasn't releasing the session's lock after being stopped, including with a <code>telepresence quit</code>. The user could be blocked as a result.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Change json output of telepresence list command</Title>\n\t<Body>\nReplace deprecated info in the JSON output of the telepresence list command.\n  </Body>\n</Note>\n## Version 2.14.4 <span style={{fontSize:'16px'}}>(August 21)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3313\">Nil pointer exception when upgrading the traffic-manager.</Title>\n\t<Body>\nUpgrading the traffic-manager using <code>telepresence helm upgrade</code> would sometimes result in a helm error message <q>executing \"telepresence/templates/intercept-env-configmap.yaml\" at &lt;.Values.intercept.environment.excluded&gt;: nil pointer evaluating interface {}.excluded\"</q>\n  </Body>\n</Note>\n## Version 2.14.2 <span style={{fontSize:'16px'}}>(July 26)</span>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3271\">Telepresence now use the OSS agent in its latest version by default.</Title>\n\t<Body>\nThe traffic manager admin was forced to set it manually during the chart installation.\n  </Body>\n</Note>\n## Version 2.14.1 <span style={{fontSize:'16px'}}>(July  7)</span>\n<Note>\n\t<Title type=\"feature\">Envoy's http idle timout is now configurable.</Title>\n\t<Body>\nA new <code>agent.helm.httpIdleTimeout</code> setting was added to the Helm chart that controls the proprietary Traffic agent's http idle timeout. The default of one hour, which in some situations would cause a lot of resource consuming and lingering connections, was changed to 70 seconds.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Add more gauges to the Traffic manager's Prometheus client.</Title>\n\t<Body>\nSeveral gauges were added to the Prometheus client to make it easier to monitor what the Traffic manager spends resources on.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Agent Pull Policy</Title>\n\t<Body>\nAdd option to set traffic agent pull policy in helm chart.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Resource leak in the Traffic manager.</Title>\n\t<Body>\nFixes a resource leak in the Traffic manager caused by lingering tunnels between the clients and Traffic agents. The tunnels are now closed correctly when terminated from the side that created them.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://www.telepresence.io/docs/reference/config#manager\">Fixed problem setting traffic manager namespace using the kubeconfig extension.</Title>\n\t<Body>\nFixes a regression introduced in version 2.10.5, making it impossible to set the traffic-manager namespace using the telepresence.io kubeconfig extension.\n  </Body>\n</Note>\n## Version 2.14.0 <span style={{fontSize:'16px'}}>(June 12)</span>\n<Note>\n\t<Title type=\"feature\" docs=\"https://github.com/telepresenceio/telepresence/pull/3172\">DNS configuration now supports excludes and mappings.</Title>\n\t<Body>\nThe DNS configuration now supports two new fields, excludes and mappings. The excludes field allows you to exclude a given list of hostnames from resolution, while the mappings field can be used to resolve a hostname with another.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"feature\">Added the ability to exclude environment variables</Title>\n\t<Body>\nAdded a new config map that can take an array of environment variables that will then be excluded from an intercept that retrieves the environment of a pod.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fixed traffic-agent backward incompatibility issue causing lack of remote mounts</Title>\n\t<Body>\nA traffic-agent of version 2.13.3 (or 1.13.15) would not propagate the directories under <code>/var/run/secrets</code> when used with a traffic manager older than 2.13.3.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/pull/2963\">Fixed race condition causing segfaults on rare occasions when a tunnel stream timed out.</Title>\n\t<Body>\nA context cancellation could sometimes be trapped in a stream reader, causing it to incorrectly return an undefined message which in turn caused the parent reader to panic on a <code>nil</code> pointer reference.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Routing conflict reporting.</Title>\n\t<Body>\nTelepresence will now attempt to detect and report routing conflicts with other running VPN software on client machines. There is a new configuration flag that can be tweaked to allow certain CIDRs to be overridden by Telepresence.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">test-vpn command deprecated</Title>\n\t<Body>\nRunning telepresence test-vpn will now print a deprecation warning and exit. The command will be removed in a future release. Instead, please configure telepresence for your VPN's routes.\n  </Body>\n</Note>\n## Version 2.13.3 <span style={{fontSize:'16px'}}>(May 25)</span>\n<Note>\n\t<Title type=\"feature\" docs=\"https://github.com/telepresenceio/telepresence/pull/3079\">Add imagePullSecrets to hooks</Title>\n\t<Body>\nAdd .Values.hooks.curl.imagePullSecrets and .Values.hooks curl.imagePullSecrets to Helm values.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"change\">Change reinvocation policy to Never for the mutating webhook</Title>\n\t<Body>\nThe default setting of the reinvocationPolicy for the mutating webhook dealing with agent injections changed from Never to IfNeeded.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3166\">Fix mounting fail of IAM roles for service accounts web identity token</Title>\n\t<Body>\nThe eks.amazonaws.com/serviceaccount volume injected by EKS is now exported and remotely mounted during an intercept.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/pull/3184\">Correct namespace selector for cluster versions with non-numeric characters</Title>\n\t<Body>\nThe mutating webhook now correctly applies the namespace selector even if the cluster version contains non-numeric characters. For example, it can now handle versions such as Major:\"1\", Minor:\"22+\".\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3179\">Enable IPv6 on the telepresence docker network</Title>\n\t<Body>\nThe \"telepresence\" Docker network will now propagate DNS AAAA queries to the Telepresence DNS resolver when it runs in a Docker container.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3171\">Fix the crash when intercepting with --local-only and --docker-run</Title>\n\t<Body>\nRunning telepresence intercept --local-only --docker-run no longer  results in a panic.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/issues/3171\">Fix incorrect error message with local-only mounts</Title>\n\t<Body>\nRunning telepresence intercept --local-only --mount false no longer results in an incorrect error message saying \"a local-only intercept cannot have mounts\".\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\" docs=\"https://github.com/telepresenceio/telepresence/pull/3161\">specify port in hook urls</Title>\n\t<Body>\nThe helm chart now correctly handles custom agentInjector.webhook.port that was not being set in hook URLs.\n  </Body>\n</Note>\n<Note>\n\t<Title type=\"bugfix\">Fix wrong default value for disableGlobal and agentArrival</Title>\n\t<Body>\nParams .intercept.disableGlobal and .timeouts.agentArrival are now correctly honored.\n  </Body>\n</Note>\n"
  },
  {
    "path": "docs/troubleshooting.md",
    "content": "---\ntitle: Troubleshooting\ndescription: \"Learn how to troubleshoot common issues related to Telepresence, including intercept issues, cluster connection issues, and errors related to Ambassador Cloud.\"\n---\n\n# Troubleshooting\n\n## Connecting to a cluster via VPN doesn't work.\n\nThere are a few different issues that could arise when working with a VPN. Please see the [dedicated page](reference/vpn.md) on Telepresence and VPNs to learn more on how to fix these.\n\n## Connecting to a cluster hosted in a Docker Container or a VM on the workstation doesn't work\n\nThe cluster probably has access to the host's network and gets confused when it is mapped by Telepresence.\nPlease check the [cluster in hosted container or vm](howtos/cluster-in-vm.md) for more details.\n\n## Volume mounts are not working on macOS\n\nIt's necessary to have `sshfs` installed in order for volume mounts to work correctly during intercepts. Lately there's been some issues using `brew install sshfs` a macOS workstation because the required component `osxfuse` (now named `macfuse`) isn't open source and hence, no longer supported. As a workaround, you can now use `gromgit/fuse/sshfs-mac` instead. Follow these steps:\n\n1. Remove old sshfs, macfuse, osxfuse using `brew uninstall`\n2. `brew install --cask macfuse`\n3. `brew install gromgit/fuse/sshfs-mac`\n4. `brew link --overwrite sshfs-mac`\n\nNow sshfs -V shows you the correct version, e.g.:\n```\n$ sshfs -V\nSSHFS version 2.10\nFUSE library version: 2.9.9\nfuse: no mount point\n```\n\n5. Next, try a mount (or an replace/ingest/intercept that performs a mount). It will fail because you need to give permission to “Benjamin Fleischer” to execute a kernel extension (a pop-up appears that takes you to the system preferences).\n6. Approve the needed permission\n7. Reboot your computer.\n\n## Volume mounts are not working on Linux\nIt's necessary to have `sshfs` installed in order for volume mounts to work correctly when Telepresence engages with remote containers.\n\nAfter you've installed `sshfs`, if mounts still aren't working:\n1. Uncomment `user_allow_other` in `/etc/fuse.conf`\n2. Add your user to the \"fuse\" group with: `sudo usermod -a -G fuse <your username>`\n3. Restart your computer after uncommenting `user_allow_other` \n\n## DNS is broken on macOS\n\nCommands like `dig` cannot find cluster resources even though Telepresence is connected to the cluster, but it works\nwith `curl`.\n\nThis is because `dig`, and some other utilities on macOS have their own built-in DNS client which bypasses the macOS\nnative DNS system and use the libc resolver directly. Here's an excerpt from the `dig` command's man-page:\n> Mac OS X NOTICE\n> \n> The nslookup command does not use the host name and address resolution or the DNS query routing\n> mechanisms used by other processes running on Mac OS X.  The results of name or address queries\n> printed by nslookup may differ from those found by other processes that use the Mac OS X native\n> name and address resolution mechanisms.  The results of DNS queries may also differ from queries\n> that use the Mac OS X DNS routing library.\n\nA command that should always work is:\n```console\n$ dscacheutil -q host -a name <name to resolve>\n```\n\n## DNS does not resolve in GitLab pipeline\n\nIf services are not resolving after running `telepresence connect` in a GitLab pipeline, this may be because the `resolv.conf` file is bind-mounted, which prevents it from being copied, deleted, or moved. However, you can still replace its contents.\n```yaml\njob:\n  ...\n  script:\n    - telepresence connect\n    - echo \"nameserver 127.0.0.1\" > /etc/resolv.conf # Telepresence runs a DNS server on port 53 but cannot update the bind-mounted resolv.conf file\n```\n\n## Helm install failes with \"uncomparable type\" error\n\nAn attempt to install the traffic-manager using the `helm` command ends with an error similar to: \n```\nError: INSTALLATION FAILED: template: telepresence-oss/templates/deployment.yaml:172:22: executing \"telepresence-oss/templates/deployment.yaml\" at <eq .initSecurityContext nil>: error calling eq: uncomparable type map[string]interface {}: map[capabilities:map[add:[NET_ADMIN]]]\n```\nThis will happen when you are using `helm` directly (as opposed to `telepresence helm`) and your helm version is older\nthan 3.11.3. To resolve this, you can upgrade your helm to a more recent version.\n\n## No Sidecar Injected in GKE private clusters\n\nAn attempt to `telepresence intercept` results in a timeout, and upon examination of the pods (`kubectl get pods`) it's discovered that the intercept command did not inject a sidecar into the workload's pods:\n\n```bash\n$ kubectl get pod\nNAME                         READY   STATUS    RESTARTS   AGE\necho-easy-7f6d54cff8-rz44k   1/1     Running   0          5m5s\n\n$ telepresence intercept echo-easy -p 8080\ntelepresence: error: connector.CreateIntercept: request timed out while waiting for agent echo-easy.default to arrive\n$ kubectl get pod\nNAME                        READY   STATUS    RESTARTS   AGE\necho-easy-d8dc4cc7c-27567   1/1     Running   0          2m9s\n\n# Notice how 1/1 containers are ready.\n```\n\nIf this is occurring in a GKE cluster with private networking enabled, it is likely due to firewall rules blocking the\nTraffic Manager's webhook injector from the API server.\nTo fix this, add a firewall rule allowing your cluster's master nodes to access TCP port `8443` in your cluster's pods,\nor change the port number that Telepresence is using for the agent injector by providing the number of an allowed port\nusing the Helm chart value `agentInjector.webhook.port`.\nPlease refer to the [telepresence install instructions](install/cloud#gke) or the [GCP docs](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules) for information to resolve this.\n\n## Injected init-container doesn't function properly\n\nThe init-container is injected to insert `iptables` rules that redirects port numbers from the app container to the\ntraffic-agent sidecar. This is necessary when the service's `targetPort` is numeric. It requires elevated privileges\n(`NET_ADMIN` capabilities), and the inserted rules may get overridden by `iptables` rules inserted by other vendors,\nsuch as Istio or Linkerd.\n\nInjection of the init-container can often be avoided by using a `targetPort` _name_ instead of a number, and  ensure\nthat  the corresponding container's `containerPort` is also named. This example uses the name \"http\", but any valid\nname will do:\n```yaml\napiVersion: v1\nkind: Pod\nmetadata:\n  ...\nspec:\n  containers:\n    - ports:\n      - name: http\n        containerPort: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  ...\nspec:\n  ports:\n    - port: 80\n      targetPort: http\n```\n\nTelepresence injects an init-container into the pods of a workload, only if at least one service specifies a numeric\n`tagertPort` that references a `containerPort` in the workload. When this isn't the case, it will instead do the\nfollowing during the injection of the traffic-agent:\n\n1. Rename the designated container's port by prefixing it (i.e., containerPort: http becomes containerPort: tm-http).\n2. Let the container port of our injected traffic-agent use the original name (i.e., containerPort: http).\n\nKubernetes takes care of the rest and will now associate the service's `targetPort` with our traffic-agent's\n`containerPort`.\n\n> [!IMPORTANT]\n> If the service is \"headless\" (using `ClusterIP: None`), then using named ports won't help because the `targetPort` will\n> not get remapped. A headless service will always require the init-container.\n\n## EKS, Calico, and Traffic Agent injection timeouts\n\nWhen using EKS with Calico CNI, the Kubernetes API server cannot reach the mutating webhook\nused for triggering the traffic agent injection. To solve this problem, try providing the\nHelm chart value `\"hostNetwork=true\"` when installing or upgrading the traffic-manager.\n\nMore information can be found in this [blog post](https://medium.com/@denisstortisilva/kubernetes-eks-calico-and-custom-admission-webhooks-a2956b49bd0d).\n\n## Error connecting to GKE or EKS cluster\n\nGKE and EKS require a plugin that utilizes their resepective IAM providers. \nYou will need to install the [gke](install/cloud#gke-authentication-plugin) or [eks](install/cloud#eks-authentication-plugin) plugins \nfor Telepresence to connect to your cluster.\n\n## Routing loops when accessing deleted or non-existent service IPs on local clusters\n\nOn local Kubernetes clusters (Kind, minikube, k3d, Docker Desktop), accessing a deleted or\nnever-assigned service ClusterIP through the Telepresence TUN device can cause a routing loop.\nThe packet is forwarded to the traffic-agent, which re-dials the same IP; with no kube-proxy\nrule in place the packet escapes the cluster via the node's default route, returns to the\nworkstation, and the cycle repeats until the connection times out.\n\nEnable the **route-controller** DaemonSet to prevent this. It installs an iptables `FORWARD`\nchain `DROP` rule for the service CIDR on every node, ensuring that packets bound for\nnon-existent ClusterIPs are silently dropped rather than escaping the cluster.\n\nSee the [Route Controller reference](reference/route-controller.md) for installation and\nconfiguration instructions.\n\n## `too many files open` error when running `telepresence connect` on Linux\n\nIf `telepresence connect` on linux fails with a message in the logs `too many files open`, then check if `fs.inotify.max_user_instances` is set too low. Check the current settings with `sysctl fs.inotify.max_user_instances` and increase it temporarily with `sudo sysctl -w fs.inotify.max_user_instances=512`. For more information about permanently increasing it see [Kernel inotify watch limit reached](https://unix.stackexchange.com/a/13757/514457).\n"
  },
  {
    "path": "docs/variables.yml",
    "content": "version: \"2.27.2\"\ndlVersion: \"v2.27.2\"\n"
  },
  {
    "path": "examples/compose/proxy-voting.override.yaml",
    "content": "x-tele:\n  connections:\n    - namespace: emojivoto\n  mounts:\n    - volumePattern: .*\n      policy: local\nservices:\n  voting:\n    x-tele:\n      type: proxy\n"
  },
  {
    "path": "examples/compose/proxy-web.override.yaml",
    "content": "x-tele:\n  connections:\n    - namespace: emojivoto\n  mounts:\n    - volumePattern: .*\n      policy: local\nservices:\n  web:\n    x-tele:\n      type: proxy\n      ports:\n        - 8080:80"
  },
  {
    "path": "examples/compose/replace.override.yaml",
    "content": "x-tele:\n  connections:\n    - namespace: emojivoto\nservices:\n  emoji:\n    x-tele:\n      type: replace\n  voting:\n    x-tele:\n      type: replace\n  vote-bot:\n    profiles:\n      - notEnabled\n"
  },
  {
    "path": "examples/docker/iam-authenticator/Dockerfile",
    "content": "FROM golang:alpine AS auth-builder\nRUN go install sigs.k8s.io/aws-iam-authenticator/cmd/aws-iam-authenticator@latest\n\n# Dockerfile with telepresence and its prerequisites\nFROM alpine\n\n# Install Telepresence prerequisites\nRUN apk add --no-cache curl iproute2 sshfs\n\n# Download and install the telepresence binary\nRUN curl -fL https://github.com/telepresenceio/telepresence/releases/download/v2.24.0-rc.0/telepresence-linux-amd64 -o telepresence && \\\n   install -o root -g root -m 0755 telepresence /usr/local/bin/telepresence\n\nCOPY --from=auth-builder /go/bin/aws-iam-authenticator ./aws-iam-authenticator\nRUN install -o root -g root -m 0755 aws-iam-authenticator /usr/local/bin/aws-iam-authenticator && \\\n    rm aws-iam-authenticator\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/telepresenceio/telepresence/v2\n\ngo 1.26\n\nrequire (\n\tgithub.com/Masterminds/sprig/v3 v3.3.0\n\tgithub.com/alexflint/go-filemutex v1.3.0\n\tgithub.com/blang/semver/v4 v4.0.0\n\tgithub.com/caarlos0/env/v11 v11.4.0\n\tgithub.com/cenkalti/backoff/v4 v4.3.0\n\tgithub.com/compose-spec/compose-go/v2 v2.10.1\n\tgithub.com/containerd/errdefs v1.0.0\n\tgithub.com/coreos/go-iptables v0.8.0\n\tgithub.com/datawire/argo-rollouts-go-client v0.0.0-20241216133646-cb1073556c99\n\tgithub.com/docker/docker v28.5.2+incompatible\n\tgithub.com/docker/go-units v0.5.0\n\tgithub.com/fsnotify/fsnotify v1.9.0\n\tgithub.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433\n\tgithub.com/godbus/dbus/v5 v5.2.2\n\tgithub.com/google/go-cmp v0.7.0\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3\n\tgithub.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb\n\tgithub.com/miekg/dns v1.1.72\n\tgithub.com/mitchellh/go-wordwrap v1.0.1\n\tgithub.com/moby/term v0.5.2\n\tgithub.com/morikuni/aec v1.1.0\n\tgithub.com/njayp/ophis v1.1.4\n\tgithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c\n\tgithub.com/pkg/sftp v1.13.10\n\tgithub.com/prometheus/client_golang v1.23.2\n\tgithub.com/puzpuzpuz/xsync/v4 v4.4.0\n\tgithub.com/rogpeppe/go-internal v1.14.1\n\tgithub.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06\n\tgithub.com/spf13/afero v1.15.0\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/spf13/pflag v1.0.10\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/telepresenceio/clog v0.0.0-20260114221933-287514cf9831\n\tgithub.com/telepresenceio/go-ftpserver v1.2.1\n\tgithub.com/telepresenceio/go-fuseftp v1.0.1\n\tgithub.com/telepresenceio/go-fuseftp/rpc v1.0.1\n\tgithub.com/telepresenceio/telepresence/cmd/cobraparser/v2 v2.0.0-20260228142840-e19ac5d889d5\n\tgithub.com/telepresenceio/telepresence/rpc/v2 v2.27.2\n\tgithub.com/vishvananda/netlink v1.3.1\n\tgolang.org/x/net v0.51.0\n\tgolang.org/x/sync v0.19.0\n\tgolang.org/x/sys v0.41.0\n\tgolang.org/x/term v0.40.0\n\tgolang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb\n\tgolang.zx2c4.com/wireguard/windows v0.5.3\n\tgoogle.golang.org/grpc v1.79.1\n\tgoogle.golang.org/protobuf v1.36.11\n\tgvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8\n\thelm.sh/helm/v3 v3.20.0\n\tk8s.io/api v0.35.2\n\tk8s.io/apimachinery v0.35.2\n\tk8s.io/cli-runtime v0.35.2\n\tk8s.io/client-go v0.35.2\n\tk8s.io/kubectl v0.35.2\n\tsigs.k8s.io/yaml v1.6.0\n)\n\nrequire (\n\tdario.cat/mergo v1.0.2 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect\n\tgithub.com/BurntSushi/toml v1.6.0 // indirect\n\tgithub.com/MakeNowJust/heredoc v1.0.0 // indirect\n\tgithub.com/Masterminds/goutils v1.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.4.0 // indirect\n\tgithub.com/Masterminds/squirrel v1.5.4 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/chai2010/gettext-go v1.0.3 // indirect\n\tgithub.com/clipperhouse/uax29/v2 v2.7.0 // indirect\n\tgithub.com/containerd/containerd v1.7.30 // indirect\n\tgithub.com/containerd/errdefs/pkg v0.3.0 // indirect\n\tgithub.com/containerd/log v0.1.0 // indirect\n\tgithub.com/containerd/platforms v0.2.1 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.6.1 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/distribution/reference v0.6.0 // indirect\n\tgithub.com/docker/go-connections v0.6.0 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.13.0 // indirect\n\tgithub.com/evanphx/json-patch v5.9.11+incompatible // indirect\n\tgithub.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect\n\tgithub.com/fatih/camelcase v1.0.0 // indirect\n\tgithub.com/fatih/color v1.18.0 // indirect\n\tgithub.com/fclairamb/ftpserverlib v0.30.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.9.0 // indirect\n\tgithub.com/go-errors/errors v1.5.1 // indirect\n\tgithub.com/go-gorp/gorp/v3 v3.1.0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.22.4 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.4 // indirect\n\tgithub.com/go-openapi/swag v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/cmdutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/conv v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/fileutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/jsonname v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/jsonutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/loading v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/mangling v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/netutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/stringutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/typeutils v0.25.4 // indirect\n\tgithub.com/go-openapi/swag/yamlutils v0.25.4 // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.5.0 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/gnostic-models v0.7.1 // indirect\n\tgithub.com/google/jsonschema-go v0.4.2 // indirect\n\tgithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect\n\tgithub.com/gosuri/uitable v0.0.4 // indirect\n\tgithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/huandu/xstrings v1.5.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jlaffaye/ftp v0.2.0 // indirect\n\tgithub.com/jmoiron/sqlx v1.4.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.18.4 // indirect\n\tgithub.com/kr/fs v0.1.0 // indirect\n\tgithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect\n\tgithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect\n\tgithub.com/lib/pq v1.11.2 // indirect\n\tgithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // 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.20 // indirect\n\tgithub.com/mattn/go-shellwords v1.0.12 // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/spdystream v0.5.0 // indirect\n\tgithub.com/moby/sys/atomicwriter v0.1.0 // indirect\n\tgithub.com/modelcontextprotocol/go-sdk v1.4.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect\n\tgithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/peterbourgon/diskv v2.0.1+incompatible // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.67.5 // indirect\n\tgithub.com/prometheus/procfs v0.20.0 // indirect\n\tgithub.com/rubenv/sql-migrate v1.8.1 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect\n\tgithub.com/segmentio/asm v1.2.1 // indirect\n\tgithub.com/segmentio/encoding v0.5.3 // indirect\n\tgithub.com/shopspring/decimal v1.4.0 // indirect\n\tgithub.com/sirupsen/logrus v1.9.4 // indirect\n\tgithub.com/spf13/cast v1.10.0 // indirect\n\tgithub.com/vishvananda/netns v0.0.5 // indirect\n\tgithub.com/winfsp/cgofuse v1.6.0 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/xhit/go-str2duration/v2 v2.1.0 // indirect\n\tgithub.com/xlab/treeprint v1.2.0 // indirect\n\tgithub.com/yosida95/uritemplate/v3 v3.0.2 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect\n\tgo.opentelemetry.io/otel v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.40.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.40.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.3 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgo.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect\n\tgolang.org/x/crypto v0.48.0 // indirect\n\tgolang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect\n\tgolang.org/x/mod v0.33.0 // indirect\n\tgolang.org/x/oauth2 v0.35.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgolang.org/x/time v0.14.0 // indirect\n\tgolang.org/x/tools v0.42.0 // indirect\n\tgolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.35.2 // indirect\n\tk8s.io/apiserver v0.35.2 // indirect\n\tk8s.io/component-base v0.35.2 // indirect\n\tk8s.io/component-helpers v0.35.2 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect\n\tk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect\n\toras.land/oras-go/v2 v2.6.0 // indirect\n\tsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect\n\tsigs.k8s.io/kustomize/api v0.21.1 // indirect\n\tsigs.k8s.io/kustomize/kyaml v0.21.1 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect\n)\n\nreplace github.com/telepresenceio/telepresence/rpc/v2 => ./rpc\n\nreplace github.com/telepresenceio/telepresence/cmd/cobraparser/v2 => ./cmd/cobraparser\n\n// Awaits https://github.com/caarlos0/env/pull/401\nreplace github.com/caarlos0/env/v11 => github.com/thallgren/env/v11 v11.0.0-20260107112108-5d5593a09332\n"
  },
  {
    "path": "go.sum",
    "content": "dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=\ndario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=\nfilippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=\nfilippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=\ngithub.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=\ngithub.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=\ngithub.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=\ngithub.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\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/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=\ngithub.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=\ngithub.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=\ngithub.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM=\ngithub.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=\ngithub.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=\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/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70=\ngithub.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\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/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80=\ngithub.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=\ngithub.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=\ngithub.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=\ngithub.com/compose-spec/compose-go/v2 v2.10.1 h1:mFbXobojGRFIVi1UknrvaDAZ+PkJfyjqkA1yseh+vAU=\ngithub.com/compose-spec/compose-go/v2 v2.10.1/go.mod h1:Ohac1SzhO/4fXXrzWIztIVB6ckmKBv1Nt5Z5mGVESUg=\ngithub.com/containerd/containerd v1.7.30 h1:/2vezDpLDVGGmkUXmlNPLCCNKHJ5BbC5tJB5JNzQhqE=\ngithub.com/containerd/containerd v1.7.30/go.mod h1:fek494vwJClULlTpExsmOyKCMUAbuVjlFsJQc4/j44M=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=\ngithub.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=\ngithub.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=\ngithub.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=\ngithub.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=\ngithub.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=\ngithub.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=\ngithub.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=\ngithub.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=\ngithub.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=\ngithub.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=\ngithub.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=\ngithub.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=\ngithub.com/datawire/argo-rollouts-go-client v0.0.0-20241216133646-cb1073556c99 h1:a+yPIx3r59bp9OnMM/CMgCleWGreM9bfIHPUatvlMJk=\ngithub.com/datawire/argo-rollouts-go-client v0.0.0-20241216133646-cb1073556c99/go.mod h1:2O7ijdMXY8T19xQXtgMYQYJwWgudnB9n358O00YqSms=\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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=\ngithub.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=\ngithub.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM=\ngithub.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU=\ngithub.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=\ngithub.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\ngithub.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=\ngithub.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=\ngithub.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=\ngithub.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=\ngithub.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=\ngithub.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=\ngithub.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=\ngithub.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=\ngithub.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=\ngithub.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=\ngithub.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=\ngithub.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=\ngithub.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=\ngithub.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=\ngithub.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=\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/fclairamb/ftpserverlib v0.30.0 h1:caB9sDn1Au//q0j2ev/icPn388qPuk4k1ajSvglDcMQ=\ngithub.com/fclairamb/ftpserverlib v0.30.0/go.mod h1:QmogtltTOgkihyKza0GNo37Mu4AEzbJ+sH6W9Y0MBIQ=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=\ngithub.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=\ngithub.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=\ngithub.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=\ngithub.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\ngithub.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=\ngithub.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=\ngithub.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=\ngithub.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4=\ngithub.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80=\ngithub.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8=\ngithub.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4=\ngithub.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=\ngithub.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=\ngithub.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=\ngithub.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=\ngithub.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4=\ngithub.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU=\ngithub.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y=\ngithub.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk=\ngithub.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI=\ngithub.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag=\ngithub.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA=\ngithub.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY=\ngithub.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo=\ngithub.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM=\ngithub.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s=\ngithub.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE=\ngithub.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48=\ngithub.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg=\ngithub.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=\ngithub.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=\ngithub.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8=\ngithub.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0=\ngithub.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw=\ngithub.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE=\ngithub.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw=\ngithub.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc=\ngithub.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4=\ngithub.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg=\ngithub.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls=\ngithub.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=\ngithub.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=\ngithub.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=\ngithub.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=\ngithub.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=\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/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=\ngithub.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=\ngithub.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=\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/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=\ngithub.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=\ngithub.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY=\ngithub.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=\ngithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=\ngithub.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=\ngithub.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw=\ngithub.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU=\ngithub.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4=\ngithub.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=\ngithub.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb h1:PGufWXXDq9yaev6xX1YQauaO1MV90e6Mpoq1I7Lz/VM=\ngithub.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=\ngithub.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=\ngithub.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jlaffaye/ftp v0.2.0 h1:lXNvW7cBu7R/68bknOX3MrRIIqZ61zELs1P2RAiA3lg=\ngithub.com/jlaffaye/ftp v0.2.0/go.mod h1:is2Ds5qkhceAPy2xD6RLI6hmp/qysSoymZ+Z2uTnspI=\ngithub.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=\ngithub.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=\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/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=\ngithub.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=\ngithub.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=\ngithub.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=\ngithub.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=\ngithub.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=\ngithub.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=\ngithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=\ngithub.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=\ngithub.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=\ngithub.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=\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.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.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=\ngithub.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=\ngithub.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=\ngithub.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=\ngithub.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=\ngithub.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=\ngithub.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=\ngithub.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=\ngithub.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=\ngithub.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=\ngithub.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=\ngithub.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=\ngithub.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=\ngithub.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=\ngithub.com/modelcontextprotocol/go-sdk v1.4.0 h1:u0kr8lbJc1oBcawK7Df+/ajNMpIDFE41OEPxdeTLOn8=\ngithub.com/modelcontextprotocol/go-sdk v1.4.0/go.mod h1:Nxc2n+n/GdCebUaqCOhTetptS17SXXNu9IfNTaLDi1E=\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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=\ngithub.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=\ngithub.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=\ngithub.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=\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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\ngithub.com/njayp/ophis v1.1.4 h1:6aq3UdU6MlGIjpg//IzKc2yIxHYMf6Rg1A5MLovM9gg=\ngithub.com/njayp/ophis v1.1.4/go.mod h1:F9KEnfeCJzCWo3e0JP2QqSCN04bXptXl1ico5lcLeYg=\ngithub.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=\ngithub.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=\ngithub.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=\ngithub.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=\ngithub.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=\ngithub.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=\ngithub.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=\ngithub.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=\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.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=\ngithub.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=\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/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=\ngithub.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\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.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=\ngithub.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=\ngithub.com/prometheus/procfs v0.20.0 h1:AA7aCvjxwAquZAlonN7888f2u4IN8WVeFgBi4k82M4Q=\ngithub.com/prometheus/procfs v0.20.0/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=\ngithub.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo=\ngithub.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo=\ngithub.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho=\ngithub.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U=\ngithub.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc=\ngithub.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnAfVjZNvfJTYfPetfZk5yoSTLaQ=\ngithub.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=\ngithub.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=\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/rubenv/sql-migrate v1.8.1 h1:EPNwCvjAowHI3TnZ+4fQu3a915OpnQoPAjTXCGOy2U0=\ngithub.com/rubenv/sql-migrate v1.8.1/go.mod h1:BTIKBORjzyxZDS6dzoiw6eAFYJ1iNlGAtjn4LGeVjS8=\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/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=\ngithub.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=\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/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 h1:PT+ElG/UUFMfqy5HrxJxNzj3QBOf7dZwupeVC+mG1Lo=\ngithub.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=\ngithub.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=\ngithub.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=\ngithub.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=\ngithub.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=\ngithub.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=\ngithub.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=\ngithub.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=\ngithub.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=\ngithub.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=\ngithub.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=\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.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=\ngithub.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/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.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.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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/telepresenceio/clog v0.0.0-20260114221933-287514cf9831 h1:STun1hGf7oDZqRChINrQekn2EfypqhB8z67WlCZGHoU=\ngithub.com/telepresenceio/clog v0.0.0-20260114221933-287514cf9831/go.mod h1:UNeuJUkHiI0y2x59WmgSNMTTCYLMU12U5AUHhFi3BOA=\ngithub.com/telepresenceio/go-ftpserver v1.2.1 h1:iv33eT7r2yptzfVU9ONj5BbZkZ3HStN6NaHZN99dBIQ=\ngithub.com/telepresenceio/go-ftpserver v1.2.1/go.mod h1:PWBrLTzbsYc4hi1mleS3MJmVyJkHMSc4Q5EAzNqB+qQ=\ngithub.com/telepresenceio/go-fuseftp v1.0.1 h1:Wrf/eefwBGlrIRXDS/fJy/PUYNLdOe8ZmYi7PsydjdM=\ngithub.com/telepresenceio/go-fuseftp v1.0.1/go.mod h1:9boAs9FH5+11hAaSMqQmuXPfFd6MpZPwfqIwWXj2868=\ngithub.com/telepresenceio/go-fuseftp/rpc v1.0.1 h1:gX85jmaIZRCzprFikoZYKTZEQMHNXmYOG0TwnLHAhwk=\ngithub.com/telepresenceio/go-fuseftp/rpc v1.0.1/go.mod h1:3mOKgPhyX0r+NGG9QK7VPM0Cf3M52V9AeL9vpZpvZ+U=\ngithub.com/thallgren/env/v11 v11.0.0-20260107112108-5d5593a09332 h1:0f3badT5ei3YJLU2oiDMWJzsD1xj3/Zy+figN91tFwA=\ngithub.com/thallgren/env/v11 v11.0.0-20260107112108-5d5593a09332/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=\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/winfsp/cgofuse v1.6.0 h1:re3W+HTd0hj4fISPBqfsrwyvPFpzqhDu8doJ9nOPDB0=\ngithub.com/winfsp/cgofuse v1.6.0/go.mod h1:uxjoF2jEYT3+x+vC2KJddEGdk/LU8pRowXmyVMHSV5I=\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/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=\ngithub.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=\ngithub.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=\ngithub.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=\ngithub.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=\ngithub.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=\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.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w=\ngo.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=\ngo.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=\ngo.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0=\ngo.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=\ngo.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=\ngo.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU=\ngo.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI=\ngo.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU=\ngo.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk=\ngo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw=\ngo.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s=\ngo.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=\ngo.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=\ngo.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=\ngo.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=\ngo.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=\ngo.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=\ngo.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=\ngo.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=\ngo.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=\ngo.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=\ngo.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=\ngo.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=\ngo.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=\ngo.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=\ngo.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngo.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U=\ngo.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=\ngolang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=\ngolang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=\ngolang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=\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.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\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.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.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=\ngolang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\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-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.16.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/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=\ngolang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngolang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=\ngolang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\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.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\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.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=\ngolang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb h1:whnFRlWMcXI9d+ZbWg+4sHnLp52d5yiIPUxMBSt4X9A=\ngolang.zx2c4.com/wireguard v0.0.0-20250521234502-f333402bd9cb/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=\ngolang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=\ngolang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto v0.0.0-20241209162323-e6fa225c2576 h1:k48HcZ4FE6in0o8IflZCkc1lTc2u37nhGd8P+fo4r24=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=\ngoogle.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=\ngopkg.in/evanphx/json-patch.v4 v4.13.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.v2 v2.2.2/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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=\ngotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=\ngvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 h1:Zy8IV/+FMLxy6j6p87vk/vQGKcdnbprwjTxc8UiUtsA=\ngvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8/go.mod h1:QkHjoMIBaYtpVufgwv3keYAbln78mBoCuShZrPrer1Q=\nhelm.sh/helm/v3 v3.20.0 h1:2M+0qQwnbI1a2CxN7dbmfsWHg/MloeaFMnZCY56as50=\nhelm.sh/helm/v3 v3.20.0/go.mod h1:rTavWa0lagZOxGfdhu4vgk1OjH2UYCnrDKE2PVC4N0o=\nk8s.io/api v0.35.2 h1:tW7mWc2RpxW7HS4CoRXhtYHSzme1PN1UjGHJ1bdrtdw=\nk8s.io/api v0.35.2/go.mod h1:7AJfqGoAZcwSFhOjcGM7WV05QxMMgUaChNfLTXDRE60=\nk8s.io/apiextensions-apiserver v0.35.2 h1:iyStXHoJZsUXPh/nFAsjC29rjJWdSgUmG1XpApE29c0=\nk8s.io/apiextensions-apiserver v0.35.2/go.mod h1:OdyGvcO1FtMDWQ+rRh/Ei3b6X3g2+ZDHd0MSRGeS8rU=\nk8s.io/apimachinery v0.35.2 h1:NqsM/mmZA7sHW02JZ9RTtk3wInRgbVxL8MPfzSANAK8=\nk8s.io/apimachinery v0.35.2/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=\nk8s.io/apiserver v0.35.2 h1:rb52v0CZGEL0FkhjS+I6jHflAp7fZ4MIaKcEHX7wmDk=\nk8s.io/apiserver v0.35.2/go.mod h1:CROJUAu0tfjZLyYgSeBsBan2T7LUJGh0ucWwTCSSk7g=\nk8s.io/cli-runtime v0.35.2 h1:3DNctzpPNXavqyrm/FFiT60TLk4UjUxuUMYbKOE970E=\nk8s.io/cli-runtime v0.35.2/go.mod h1:G2Ieu0JidLm5m1z9b0OkFhnykvJ1w+vjbz1tR5OFKL0=\nk8s.io/client-go v0.35.2 h1:YUfPefdGJA4aljDdayAXkc98DnPkIetMl4PrKX97W9o=\nk8s.io/client-go v0.35.2/go.mod h1:4QqEwh4oQpeK8AaefZ0jwTFJw/9kIjdQi0jpKeYvz7g=\nk8s.io/component-base v0.35.2 h1:btgR+qNrpWuRSuvWSnQYsZy88yf5gVwemvz0yw79pGc=\nk8s.io/component-base v0.35.2/go.mod h1:B1iBJjooe6xIJYUucAxb26RwhAjzx0gHnqO9htWIX+0=\nk8s.io/component-helpers v0.35.2 h1:7Ea4CDgHnyOGrl3ZhD8e46SdTyf1itTONnreJ2Q52UM=\nk8s.io/component-helpers v0.35.2/go.mod h1:ybIoc8i92FG7xJFrBcEMzB8ul1wlZgfF0I4Z9w0V6VQ=\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-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY=\nk8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=\nk8s.io/kubectl v0.35.2 h1:aSmqhSOfsoG9NR5oR8OD5eMKpLN9x8oncxfqLHbJJII=\nk8s.io/kubectl v0.35.2/go.mod h1:+OJC779UsDJGxNPbHxCwvb4e4w9Eh62v/DNYU2TlsyM=\nk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=\nk8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=\noras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=\noras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=\nsigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=\nsigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs=\nsigs.k8s.io/kustomize/api v0.21.1/go.mod h1:f3wkKByTrgpgltLgySCntrYoq5d3q7aaxveSagwTlwI=\nsigs.k8s.io/kustomize/kyaml v0.21.1 h1:IVlbmhC076nf6foyL6Taw4BkrLuEsXUXNpsE+ScX7fI=\nsigs.k8s.io/kustomize/kyaml v0.21.1/go.mod h1:hmxADesM3yUN2vbA5z1/YTBnzLJ1dajdqpQonwBL1FQ=\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/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=\nsigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=\nsigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=\nsigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=\n"
  },
  {
    "path": "integration_test/agent_injector_disabled_test.go",
    "content": "package integration_test\n\nimport (\n\t\"os\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n)\n\ntype agentInjectorDisabledSuite struct {\n\titest.Suite\n\titest.NamespacePair\n\tlogName string\n}\n\nfunc (s *agentInjectorDisabledSuite) SuiteName() string {\n\treturn \"AgentInjectorDisabled\"\n}\n\nfunc init() {\n\titest.AddNamespacePairSuite(\"\", func(h itest.NamespacePair) itest.TestingSuite {\n\t\treturn &agentInjectorDisabledSuite{Suite: itest.Suite{Harness: h}, NamespacePair: h}\n\t})\n}\n\nfunc (s *agentInjectorDisabledSuite) SetupSuite() {\n\ts.Suite.SetupSuite()\n\ts.logName = s.TelepresenceHelmInstallOK(s.Context(), false, \"--set\", \"agentInjector.enabled=false\")\n}\n\nfunc (s *agentInjectorDisabledSuite) TearDownSuite() {\n\ts.UninstallTrafficManager(s.Context(), s.ManagerNamespace())\n}\n\nfunc (s *agentInjectorDisabledSuite) Test_AgentInjectorDisabled() {\n\tconst svc = \"echo-easy\"\n\tctx := s.Context()\n\n\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\ts.TelepresenceConnect(ctx)\n\t_, sErr, err := itest.Telepresence(ctx, \"intercept\", svc)\n\ts.Error(err)\n\ts.Contains(sErr, \"agent-injector is disabled\")\n\titest.TelepresenceQuitOk(ctx)\n\n\tlogData, err := os.ReadFile(s.logName)\n\ts.Require().NoError(err)\n\n\tlogs := string(logData)\n\ts.NotContains(logs, \"Using traffic-agent image\")\n\ts.Contains(logs, \"Cluster domain derived from /etc/resolv.conf\")\n}\n\nfunc (s *agentInjectorDisabledSuite) Test_VersionWithAgentInjectorDisabled() {\n\tctx := s.Context()\n\trq := s.Require()\n\trestartCount := func() int {\n\t\tpods := itest.RunningPods(ctx, agentconfig.ManagerAppName, s.ManagerNamespace())\n\t\tif len(pods) == 1 {\n\t\t\tfor _, cs := range pods[0].Status.ContainerStatuses {\n\t\t\t\tif cs.Name == agentconfig.ManagerAppName {\n\t\t\t\t\treturn int(cs.RestartCount)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn -1\n\t}\n\toldRestartCount := restartCount()\n\trq.GreaterOrEqual(oldRestartCount, 0)\n\ts.TelepresenceConnect(ctx)\n\tsr, err := itest.TelepresenceStatus(ctx)\n\titest.TelepresenceQuitOk(ctx)\n\trq.NoError(err)\n\ttm := sr.TrafficManager\n\trq.NotNil(tm)\n\trq.Empty(tm.TrafficAgent)\n\n\t// Verify that traffic-manager didn't crash\n\trq.Equal(oldRestartCount, restartCount())\n}\n\nfunc (s *agentInjectorDisabledSuite) Test_ManualAgent() {\n\ttestManualAgent(&s.Suite, s.NamespacePair)\n}\n"
  },
  {
    "path": "integration_test/also_proxy_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc (s *notConnectedSuite) Test_AlsoProxy32() {\n\tconst ipToTest = \"10.10.74.1\"\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx, \"--also-proxy\", ipToTest+\"/32\", \"--name\", \"ax\")\n\titest.TelepresenceOk(ctx, \"loglevel\", \"trace\")\n\tdefer itest.TelepresenceQuitOk(ctx)\n\tdefer itest.TelepresenceOk(ctx, \"loglevel\", \"debug\")\n\n\trq := s.Require()\n\tlogFile := filepath.Join(filelocation.AppUserLogDir(s.Context()), \"daemon.log\")\n\trootLog, err := os.Open(logFile)\n\trq.NoError(err)\n\tdefer rootLog.Close()\n\n\t// Figure out where the current end of the logfile is. This must be done before any\n\t// of the tests run because the queries that the DNS resolver receives are dependent\n\t// on how the system's DNS resolver handles search paths and caching.\n\tst, err := rootLog.Stat()\n\trq.NoError(err)\n\tpos := st.Size()\n\n\t// Make an attempt to curl the also-proxied IP. The attempt will fail (there's nothing at the\n\t// other end), and that's OK. We're just interested in seeing it logged.\n\t_, _ = itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"1\", ipToTest) //nolint:dogsled // X\n\n\t// Verify that the attempt is visible in the root log.\n\t_, err = rootLog.Seek(pos, io.SeekStart)\n\trq.NoError(err)\n\tscn := bufio.NewScanner(rootLog)\n\tfound := false\n\n\t// mustHaveWanted caters for cases where the default behavior from the system's resolver\n\t// is to not send unwanted queries to our resolver at all (based on search and routes).\n\t// It is forced to true for inclusion tests.\n\tstrToFind := fmt.Sprintf(\"%s:80, code STREAM_INFO\", ipToTest)\n\tfor scn.Scan() {\n\t\ttxt := scn.Text()\n\t\tif strings.Contains(txt, strToFind) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\ts.Truef(found, \"Unable to find %q\", strToFind)\n}\n"
  },
  {
    "path": "integration_test/argo_rollouts_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype argoRolloutsSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *argoRolloutsSuite) SuiteName() string {\n\treturn \"ArgoRollouts\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"-argo-rollouts\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &argoRolloutsSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *argoRolloutsSuite) SetupSuite() {\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\trq := s.Require()\n\titest.CreateNamespaces(ctx, \"argo-rollouts\")\n\tarExe := filepath.Join(itest.BuildOutput(ctx), \"bin\", \"kubectl-argo-rollouts\")\n\tif runtime.GOOS == \"windows\" {\n\t\tarExe += \".exe\"\n\t}\n\tif _, err := os.Stat(arExe); err != nil {\n\t\trq.ErrorIs(err, os.ErrNotExist)\n\t\trq.NoError(downloadKubectlArgoRollouts(ctx, arExe))\n\t}\n\tout, err := itest.KubectlOut(ctx, \"\", \"argo\", \"rollouts\", \"version\")\n\trq.NoError(err)\n\tclog.Info(ctx, out)\n\trq.NoError(itest.Kubectl(ctx, \"argo-rollouts\", \"apply\", \"-f\", \"https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml\"))\n}\n\nfunc (s *argoRolloutsSuite) TearDownSuite() {\n\titest.DeleteNamespaces(s.Context(), \"argo-rollouts\")\n}\n\nfunc downloadKubectlArgoRollouts(ctx context.Context, arExe string) error {\n\tdu := fmt.Sprintf(\n\t\t\"https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-%s-%s\",\n\t\truntime.GOOS, runtime.GOARCH)\n\tclog.Infof(ctx, \"Downloading %s\", du)\n\tresp, err := http.Get(du)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"expected status 200 OK, got %v\", resp.Status)\n\t}\n\tarExeFile, err := os.OpenFile(arExe, os.O_WRONLY|os.O_CREATE, 0o755)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = io.Copy(arExeFile, resp.Body)\n\t_ = arExeFile.Close()\n\treturn err\n}\n\nfunc (s *argoRolloutsSuite) Test_SuccessfullyInterceptsArgoRollout() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"workloads.argoRollouts.enabled=true\")\n\tdefer s.RollbackTM(ctx)\n\n\ttp, svc, port := \"Rollout\", \"echo-argo-rollout\", \"9094\"\n\ts.ApplyApp(ctx, svc, strings.ToLower(tp)+\"/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"rollout\", svc)\n\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\")\n\t\t\treturn err == nil && strings.Contains(stdout, svc)\n\t\t},\n\t\t6*time.Second, // waitFor\n\t\t2*time.Second, // polling interval\n\t)\n\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", \"--port\", port, svc)\n\trequire.Contains(stdout, \"Using \"+tp+\" \"+svc)\n\trequire.Eventually(func() bool {\n\t\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\t\treturn strings.Contains(stdout, svc+\": intercepted\") && !strings.Contains(stdout, \"Volume Mount Point\")\n\t}, 14*time.Second, 2*time.Second)\n\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\titest.TelepresenceOk(ctx, \"leave\", svc)\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\trequire.NotContains(stdout, svc+\": intercepted\")\n\n\tif !s.ClientIsVersion(\">2.21.x\") && s.ManagerIsVersion(\">2.21.x\") {\n\t\t// An <2.22.0 client will not be able to uninstall an agent when the traffic-manager is >=2.22.0\n\t\t// because the client will attempt to remove the entry in the telepresence-agents configmap. It\n\t\t// is no longer present in versions >=2.22.0\n\t\treturn\n\t}\n\ttime.Sleep(3 * time.Second)\n\titest.TelepresenceOk(ctx, \"uninstall\", svc)\n\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--agents\")\n\t\t\treturn err == nil && !strings.Contains(stdout, svc)\n\t\t},\n\t\t180*time.Second, // waitFor\n\t\t6*time.Second,   // polling interval\n\t)\n}\n\nfunc (s *argoRolloutsSuite) Test_ListsReplicaSetWhenRolloutDisabled() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\ttp, svc := \"Rollout\", \"echo-argo-rollout\"\n\ts.ApplyApp(ctx, svc, strings.ToLower(tp)+\"/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"rollout\", svc)\n\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\")\n\t\t\tclog.Info(ctx, stdout)\n\t\t\treturn err == nil && strings.Contains(stdout, svc+\"-\")\n\t\t},\n\t\t6*time.Second, // waitFor\n\t\t2*time.Second, // polling interval\n\t)\n}\n"
  },
  {
    "path": "integration_test/bind_to_podip_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n)\n\nfunc (s *connectedSuite) Test_BindToPodIP() {\n\tconst svcPfx = \"echo-server\"\n\ttplPath := filepath.Join(\"testdata\", \"k8s\", \"generic.goyaml\")\n\n\tfor i, targetPort := range []string{\"8080\", \"http\"} {\n\t\ts.Run(\"TargetPort:\"+targetPort, func() {\n\t\t\tctx := s.Context()\n\t\t\tsvc := fmt.Sprintf(\"%s-%d\", svcPfx, i)\n\t\t\tsvcPort, cancel := itest.StartLocalHttpEchoServer(ctx, svc)\n\t\t\tdefer cancel()\n\t\t\ttpl := &itest.Generic{\n\t\t\t\tName:       svc,\n\t\t\t\tTargetPort: targetPort,\n\t\t\t\tRegistry:   \"ghcr.io/telepresenceio\",\n\t\t\t\tImage:      \"echo-server:latest\",\n\t\t\t\tEnvironment: []core.EnvVar{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  \"PORTS\",\n\t\t\t\t\t\tValue: \"8080\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"LISTEN_ADDRESS\",\n\t\t\t\t\t\tValueFrom: &core.EnvVarSource{\n\t\t\t\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\t\t\t\tFieldPath: \"status.podIP\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tannotation.InjectTrafficAgent: \"enabled\",\n\t\t\t\t},\n\t\t\t}\n\t\t\ts.ApplyTemplate(ctx, tplPath, tpl)\n\t\t\trq := s.Require()\n\t\t\trq.NoError(s.RolloutStatusWait(ctx, \"deploy/\"+svc))\n\n\t\t\tdefer s.DeleteTemplate(ctx, tplPath, tpl)\n\n\t\t\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc, \"--port\", strconv.Itoa(svcPort))\n\t\t\trq.Contains(stdout, \"Using Deployment \"+svc)\n\n\t\t\titest.PingInterceptedEchoServer(ctx, svc, \"80\")\n\t\t\titest.TelepresenceOk(ctx, \"leave\", svc)\n\n\t\t\t// Ensure that we now reach the original app again.\n\t\t\ts.Eventually(func() bool {\n\t\t\t\tout, err := itest.Output(ctx, \"curl\", \"--verbose\", \"--max-time\", \"0.5\", svc)\n\t\t\t\tclog.Infof(ctx, \"Received %s\", out)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"curl error %v\", err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}, 30*time.Second, 2*time.Second, \"Pod app is not reachable after ending the intercept\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "integration_test/cidr_conflict_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\n\t\"github.com/go-json-experiment/json\"\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/slice\"\n)\n\ntype cidrConflictSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tvipSubnet netip.Prefix\n\tsubnets   []netip.Prefix\n\tscripts   string\n}\n\nfunc (s *cidrConflictSuite) SuiteName() string {\n\treturn \"CIDRConflict\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &cidrConflictSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *cidrConflictSuite) SetupSuite() {\n\tif runtime.GOOS != \"linux\" {\n\t\ts.T().Skip(\"we can only create veth interfaces on linux\")\n\t}\n\tconst svc = \"echo\"\n\ts.Suite.SetupSuite()\n\ttpl := &itest.Generic{\n\t\tName:     svc,\n\t\tRegistry: \"ghcr.io/telepresenceio\",\n\t\tImage:    \"echo-server:latest\",\n\t\tEnvironment: []core.EnvVar{\n\t\t\t{\n\t\t\t\tName:  \"PORTS\",\n\t\t\t\tValue: \"8080\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"LISTEN_ADDRESS\",\n\t\t\t\tValueFrom: &core.EnvVarSource{\n\t\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\t\tFieldPath: \"status.podIP\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tAnnotations: map[string]string{\n\t\t\tannotation.InjectTrafficAgent: \"enabled\",\n\t\t},\n\t}\n\ts.ApplyTemplate(s.Context(), filepath.Join(\"testdata\", \"k8s\", \"generic.goyaml\"), &tpl)\n\ts.NoError(s.RolloutStatusWait(s.Context(), \"deploy/echo\"))\n\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tst := itest.TelepresenceStatusOk(ctx)\n\titest.TelepresenceQuit(ctx)\n\ts.subnets = st.RootDaemon.Subnets\n\tif len(s.subnets) < 2 {\n\t\ts.T().Skip(\"Test cannot run unless client maps at least two subnets\")\n\t}\n\tvar err error\n\ts.scripts, err = filepath.Abs(filepath.Join(\"testdata\", \"scripts\"))\n\tif s.NoError(err) {\n\t\t// Create an interface that will be in conflict with the service and pod subnets.\n\t\ts.NoError(itest.Run(ctx, \"sudo\", filepath.Join(s.scripts, \"veth-up.sh\"), s.subnets[0].String(), s.subnets[1].String()))\n\t\ts.NoError(err)\n\t}\n\ts.vipSubnet = client.GetConfig(ctx).Routing().VirtualSubnet\n}\n\nfunc (s *cidrConflictSuite) TearDownSuite() {\n\tctx := s.Context()\n\ts.NoError(itest.Run(ctx, \"sudo\", filepath.Join(s.scripts, \"veth-down.sh\"), s.subnets[0].String(), s.subnets[1].String()))\n\ts.DeleteSvcAndWorkload(ctx, \"deploy\", \"echo\")\n}\n\nfunc (s *cidrConflictSuite) Test_AutoConflictResolution() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tst := itest.TelepresenceStatusOk(ctx)\n\tdefer itest.TelepresenceQuit(ctx)\n\tsns := st.RootDaemon.Subnets\n\trq := s.Require()\n\trq.Less(len(sns), len(s.subnets), \"pod and service subnets should be combined into one virtual subnet\")\n\n\t// The first subnet must now be virtual.\n\tviSn := sns[0]\n\trq.Equalf(s.vipSubnet, viSn, \"expected %s to be a virtual CIDR\", viSn)\n\n\t// Ingest to get a container environment.\n\tenvFile := filepath.Join(s.T().TempDir(), \"echo.env\")\n\titest.TelepresenceOk(ctx, \"ingest\", \"echo\", \"--env-file\", envFile, \"--env-syntax\", \"json\")\n\titest.TelepresenceOk(ctx, \"leave\", \"echo\")\n\tvar env map[string]string\n\tenvData, err := os.ReadFile(envFile)\n\trq.NoError(err)\n\terr = json.Unmarshal(envData, &env)\n\trq.NoError(err)\n\n\t// Verify that these IPs in the environment have been translated into virtual IPs.\n\tfor _, key := range []string{\"LISTEN_ADDRESS\", \"ECHO_SERVICE_HOST\"} {\n\t\taddrVal, ok := env[key]\n\t\trq.True(ok)\n\t\taddr, err := netip.ParseAddr(addrVal)\n\t\trq.NoError(err)\n\t\trq.Truef(viSn.Contains(addr), \"virtual subnet %s does not contain %s %s\", viSn, key, addr)\n\t}\n}\n\nfunc (s *cidrConflictSuite) Test_AutoConflictAvoidance() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx, \"--allow-conflicting-subnets\", fmt.Sprintf(\"%s,%s\", s.subnets[0], s.subnets[1]))\n\tst := itest.TelepresenceStatusOk(ctx)\n\tdefer itest.TelepresenceQuitOk(ctx)\n\tsns := st.RootDaemon.Subnets\n\ts.Require().Equal(slice.AsStrings(s.subnets), slice.AsStrings(sns), \"subnet conflict should not be resolved using VNAT\")\n}\n\nfunc (s *cidrConflictSuite) Test_AutoConflictResolution_CloudDisable() {\n\tctx := s.Context()\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"client.routing.autoResolveConflicts=false\")\n\tdefer s.RollbackTM(ctx)\n\n\t_, err := s.TelepresenceTryConnect(ctx)\n\ts.Require().Error(err)\n}\n\nfunc (s *cidrConflictSuite) Test_AutoConflictResolution_ClientDisable() {\n\tctx := itest.WithConfig(s.Context(), func(cfg client.Config) {\n\t\tcfg.Routing().AutoResolveConflicts = false\n\t})\n\t_, err := s.TelepresenceTryConnect(ctx)\n\ts.Require().Error(err)\n}\n\nfunc (s *cidrConflictSuite) Test_AllowConflictResolution() {\n\tctx := itest.WithConfig(s.Context(), func(cfg client.Config) {\n\t\tcfg.Routing().AutoResolveConflicts = false\n\t\tcfg.Routing().AllowConflicting = s.subnets\n\t})\n\n\ttestIP := net.IP(s.subnets[0].Addr().AsSlice())\n\ttestIP[len(testIP)-1] = 37\n\n\t// Verify that a route in the conflicting subnet is routed via brm\n\tout, err := itest.Output(ctx, \"ip\", \"route\", \"get\", testIP.String())\n\trq := s.Require()\n\trq.NoError(err)\n\trq.Contains(out, \"dev brm\")\n\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceQuitOk(ctx)\n\tst := itest.TelepresenceStatusOk(ctx)\n\tdefer itest.TelepresenceQuitOk(ctx)\n\tsns := st.RootDaemon.Subnets\n\trq.Equal(sns, s.subnets, \"Subnets should not change but %v != %v\", sns, s.subnets)\n\n\t// Verify that a route in the conflicting subnet is routed via Telepresence\n\tout, err = itest.Output(ctx, \"ip\", \"route\", \"get\", testIP.String())\n\trq.NoError(err)\n\trq.Contains(out, \"dev tel0\") // tel0 is OK, we only run this on linux\n}\n"
  },
  {
    "path": "integration_test/cli_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\ntype cliSuite struct {\n\titest.Suite\n}\n\nfunc (s *cliSuite) SuiteName() string {\n\treturn \"CLI\"\n}\n\nfunc init() {\n\titest.AddClusterSuite(func(ctx context.Context) itest.TestingSuite {\n\t\treturn &cliSuite{Suite: itest.Suite{Harness: itest.NewContextHarness(ctx)}}\n\t})\n}\n\nfunc (s *cliSuite) Test_Version() {\n\tstdout, stderr, err := itest.Telepresence(s.Context(), \"version\")\n\tif err != nil {\n\t\ts.SetGeneralError(fmt.Errorf(\"bailing out. If telepresence version isn't working, nothing will: %w\", err))\n\t\ts.Require().NoError(err)\n\t}\n\ts.Empty(stderr)\n\ts.Regexp(fmt.Sprintf(`Client\\s*: v%s`, regexp.QuoteMeta(s.ClientVersion().String())), stdout)\n}\n\nfunc (s *cliSuite) Test_VersionWithInvalidKubeContext() {\n\tstdout, _, err := itest.Telepresence(itest.WithEnv(s.Context(), map[string]string{\n\t\t\"KUBECONFIG\": \"file-that-does-not-exist\",\n\t}), \"version\")\n\tif err != nil {\n\t\ts.Require().NoError(err)\n\t}\n\n\ts.Regexp(fmt.Sprintf(`Client\\s*: v%s`, regexp.QuoteMeta(s.ClientVersion().String())), stdout)\n}\n\nfunc (s *cliSuite) Test_Help() {\n\t// TODO: Fix these tests\n\ts.T().Skip(\"these tests don't work\")\n\tconst (\n\t\thelpHead  = `Telepresence can connect to a cluster and route all outbound traffic`\n\t\tusageHead = `Usage:`\n\t)\n\n\tstdout, stderr, err := itest.Telepresence(s.Context(), \"help\")\n\tif err != nil {\n\t\ts.SetGeneralError(fmt.Errorf(\"bailing out. If telepresence help isn't working, nothing will: %w\", err))\n\t\ts.Require().NoError(err)\n\t}\n\ts.Empty(stderr)\n\ts.Contains(stdout, helpHead)\n\ts.Contains(stdout, usageHead)\n\n\tstdout, stderr, err = itest.Telepresence(s.Context(), \"--help\")\n\tif err != nil {\n\t\ts.SetGeneralError(fmt.Errorf(\"bailing out. If telepresence --help isn't working, nothing will: %w\", err))\n\t\ts.Require().NoError(err)\n\t}\n\ts.Empty(stderr)\n\ts.Contains(stdout, helpHead)\n\ts.Contains(stdout, usageHead)\n\n\tstdout, stderr, err = itest.Telepresence(s.Context(), \"-h\")\n\tif err != nil {\n\t\ts.SetGeneralError(fmt.Errorf(\"bailing out. If telepresence --help isn't working, nothing will: %w\", err))\n\t\ts.Require().NoError(err)\n\t}\n\ts.Empty(stderr)\n\ts.Contains(stdout, helpHead)\n\ts.Contains(stdout, usageHead)\n\n\tstdout, stderr, err = itest.Telepresence(s.Context(), \"\")\n\tif err != nil {\n\t\ts.SetGeneralError(fmt.Errorf(\"bailing out. If telepresence --help isn't working, nothing will: %w\", err))\n\t\ts.Require().NoError(err)\n\t}\n\ts.Empty(stderr)\n\ts.Contains(stdout, helpHead)\n\ts.Contains(stdout, usageHead)\n}\n\nfunc (s *cliSuite) Test_Status() {\n\titest.TelepresenceQuitOk(s.Context())\n\tstdout, stderr, err := itest.Telepresence(s.Context(), \"status\")\n\tif err != nil {\n\t\ts.SetGeneralError(fmt.Errorf(\"bailing out. If telepresence status isn't working, nothing will: %w\", err))\n\t\ts.Require().NoError(err)\n\t}\n\ts.NoError(err)\n\ts.Empty(stderr)\n\ts.Contains(stdout, \"Root Daemon: Not running\")\n\ts.Contains(stdout, \"User Daemon: Not running\")\n}\n\nfunc (s *cliSuite) Test_StatusWithJSON() {\n\titest.TelepresenceQuitOk(s.Context())\n\tstatus, err := itest.TelepresenceStatus(s.Context())\n\tif err != nil {\n\t\ts.SetGeneralError(fmt.Errorf(\"bailing out. If telepresence status isn't working, nothing will: %w\", err))\n\t\ts.Require().NoError(err)\n\t}\n\ts.False(status.RootDaemon.Running)\n\ts.False(status.UserDaemon.Running)\n}\n\nfunc (s *cliSuite) Test_ConfigViewClientOnly() {\n\tctx := itest.WithConfig(s.Context(), func(c client.Config) {\n\t\tc.Timeouts().PrivateConnectivityCheck = 0\n\t})\n\tout := itest.TelepresenceOk(ctx, \"config\", \"view\", \"--client-only\")\n\t// Ensure that zero (but not default) values are included in output\n\ts.Regexp(regexp.MustCompile(`\\sconnectivityCheck\\s*:\\s*0s\\n`), out)\n}\n"
  },
  {
    "path": "integration_test/cloud_config_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"log/slog\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n)\n\nfunc (s *notConnectedSuite) Test_CloudNeverProxy() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\tsvcName := \"echo-never-proxy\"\n\titest.ApplyEchoService(ctx, svcName, s.AppNamespace(), 8080)\n\tdefer itest.DeleteSvcAndWorkload(ctx, \"deploy\", svcName, s.AppNamespace())\n\n\tipStr, err := itest.Output(ctx, \"kubectl\",\n\t\t\"--namespace\", s.AppNamespace(),\n\t\t\"get\", \"svc\", svcName,\n\t\t\"-o\",\n\t\t\"jsonpath={.spec.clusterIP}\")\n\trequire.NoError(err)\n\tip, err := netip.ParseAddr(ipStr)\n\trequire.NoError(err)\n\tif ip.IsLoopback() {\n\t\ts.T().Skipf(\"test can't run on host with a loopback cluster IP %s\", ip)\n\t}\n\n\tmask := 32\n\tif s.IsIPv6() {\n\t\tmask = 128\n\t}\n\n\tkc := itest.KubeConfig(ctx)\n\tcfg, err := clientcmd.LoadFromFile(kc)\n\trequire.NoError(err)\n\tktx := cfg.Contexts[cfg.CurrentContext]\n\trequire.NotNil(ktx, \"unable to get current context from config\")\n\tcluster := cfg.Clusters[ktx.Cluster]\n\trequire.NotNil(cluster, \"unable to get %s cluster from config\", ktx.Cluster)\n\tips, err := getClusterIPs(cluster)\n\trequire.NoError(err)\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", fmt.Sprintf(\"client.routing.neverProxySubnets={%s/%d}\", ip, mask))\n\tdefer s.RollbackTM(ctx)\n\n\ttimeout := 20 * time.Second\n\tif runtime.GOOS == \"windows\" {\n\t\ttimeout *= 5\n\t}\n\ts.Eventuallyf(func() bool {\n\t\tdefer func() {\n\t\t\tstdout, stderr, err := itest.Telepresence(ctx, \"quit\")\n\t\t\tclog.Infof(ctx, \"stdout: %q\", stdout)\n\t\t\tclog.Infof(ctx, \"stderr: %q\", stderr)\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t}()\n\t\tstdout, stderr, err := itest.Telepresence(ctx, \"connect\", \"--namespace\", s.AppNamespace(), \"--manager-namespace\", s.ManagerNamespace())\n\t\tclog.Infof(ctx, \"stdout: %q\", stdout)\n\t\tclog.Infof(ctx, \"stderr: %q\", stderr)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t\treturn false\n\t\t}\n\n\t\tneverProxiedCount := 1\n\n\t\t// The cluster's IP address will be never proxied unless it's a loopback, so we gotta account for that.\n\t\tfor _, cip := range ips {\n\t\t\tif !cip.IsLoopback() {\n\t\t\t\tneverProxiedCount++\n\t\t\t}\n\t\t}\n\n\t\tstdout, stderr, err = itest.Telepresence(ctx, \"status\")\n\t\tclog.Infof(ctx, \"stdout: %q\", stdout)\n\t\tclog.Infof(ctx, \"stderr: %q\", stderr)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tm := regexp.MustCompile(`Never Proxy\\s*:\\s*\\((\\d+) subnets\\)`).FindStringSubmatch(stdout)\n\t\tnpcOk := false\n\t\tif m != nil {\n\t\t\tnpc, _ := strconv.Atoi(m[1])\n\t\t\tnpcOk = npc > 0 && npc <= neverProxiedCount\n\t\t}\n\t\tif !npcOk {\n\t\t\tclog.Errorf(ctx, \"did not find 1-%d never-proxied subnets\", neverProxiedCount)\n\t\t\treturn false\n\t\t}\n\n\t\tview, err := s.configView()\n\t\trequire.NoError(err)\n\t\tnpc := len(view.Config.Routing().NeverProxy)\n\t\tnpcOk = npc > 0 && npc <= neverProxiedCount\n\t\tif !npcOk {\n\t\t\tclog.Errorf(ctx, \"did not find 1-%d never-proxied subnets in json status\", neverProxiedCount)\n\t\t\treturn false\n\t\t}\n\n\t\tif itest.Run(ctx, \"curl\", \"--silent\", \"--max-time\", \"0.5\", ip.String()) == nil {\n\t\t\tclog.Errorf(ctx, \"never-proxied IP %s is reachable\", ip)\n\t\t\treturn false\n\t\t}\n\n\t\tclog.Infof(ctx, \"Success! Never-proxied IP %s is not reachable\", ip)\n\t\treturn true\n\t}, timeout, 5*time.Second, \"never-proxy not updated in %s\", timeout)\n}\n\nfunc (s *notConnectedSuite) Test_CloudAllowConflicting() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\tacs, err := netip.ParsePrefix(\"10.88.2.4/30\")\n\trequire.NoError(err)\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", fmt.Sprintf(\"client.routing.allowConflictingSubnets={%s}\", acs))\n\tdefer s.RollbackTM(ctx)\n\n\ttimeout := 20 * time.Second\n\tif runtime.GOOS == \"windows\" {\n\t\ttimeout *= 5\n\t}\n\ts.Eventuallyf(func() bool {\n\t\tdefer func() {\n\t\t\tstdout, stderr, err := itest.Telepresence(ctx, \"quit\")\n\t\t\tclog.Infof(ctx, \"stdout: %q\", stdout)\n\t\t\tclog.Infof(ctx, \"stderr: %q\", stderr)\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t}()\n\t\ts.TelepresenceConnect(ctx)\n\t\tsr, err := itest.TelepresenceStatus(ctx)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tac := sr.RootDaemon.AllowConflicting\n\t\tif len(ac) != 1 {\n\t\t\treturn false\n\t\t}\n\t\treturn len(ac) == 1 && netip.MustParsePrefix(ac[0].String()) == acs\n\t}, timeout, 5*time.Second, \"allow-conflicting-subnets not updated in %s\", timeout)\n}\n\nfunc (s *notConnectedSuite) configView() (*client.SessionConfig, error) {\n\tstdout, _, err := itest.Telepresence(s.Context(), \"config\", \"view\", \"--output\", \"json\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar view client.SessionConfig\n\terr = json.Unmarshal([]byte(stdout), &view, false)\n\treturn &view, err\n}\n\nfunc (s *notConnectedSuite) Test_CloudAgentArrival() {\n\tctx := s.Context()\n\tconst agentArrivalTimeout = 2 * time.Minute\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", fmt.Sprintf(\"timeouts.agentArrival=%s\", agentArrivalTimeout))\n\tdefer s.RollbackTM(ctx)\n\n\ttimeout := 20 * time.Second\n\tif runtime.GOOS == \"windows\" {\n\t\ttimeout *= 5\n\t}\n\n\ts.Eventuallyf(func() bool {\n\t\tdefer func() {\n\t\t\tstdout, stderr, err := itest.Telepresence(ctx, \"quit\")\n\t\t\tclog.Infof(ctx, \"stdout: %q\", stdout)\n\t\t\tclog.Infof(ctx, \"stderr: %q\", stderr)\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t}()\n\t\ts.TelepresenceConnect(ctx)\n\t\tview, err := s.configView()\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn view.Timeouts().Get(client.TimeoutIntercept) == agentArrivalTimeout\n\t}, timeout, 5*time.Second, \"timeouts.intercept not updated by changing traffic-manager's timeouts.agentArrival in %s\", timeout)\n}\n\nfunc (s *notConnectedSuite) Test_RootdCloudLogLevel() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\trootLogName := filepath.Join(filelocation.AppUserLogDir(ctx), \"daemon.log\")\n\t// Figure out where the current end of the logfile is.\n\tpos := int64(0)\n\tst, err := os.Stat(rootLogName)\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\ts.T().Fatalf(\"Unexpected error stat'ing %s: %v\", rootLogName, err)\n\t\t}\n\t} else {\n\t\tpos = st.Size()\n\t}\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"logLevel=debug,agent.logLevel=debug,client.logLevels.rootDaemon=trace\")\n\tdefer s.RollbackTM(ctx)\n\n\tctx = itest.WithConfig(ctx, func(cfg client.Config) {\n\t\tcfg.LogLevels().RootDaemon = slog.LevelInfo\n\t})\n\n\ts.Eventually(func() bool {\n\t\t_, _, err = itest.Telepresence(ctx, \"connect\", \"--namespace\", s.AppNamespace(), \"--manager-namespace\", s.ManagerNamespace())\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\titest.TelepresenceDisconnectOk(ctx)\n\n\t\trootLog, err := os.Open(rootLogName)\n\t\trequire.NoError(err)\n\t\tdefer rootLog.Close()\n\t\t_, err = rootLog.Seek(pos, 0)\n\t\trequire.NoError(err)\n\n\t\tlevelSet := false\n\t\tscn := bufio.NewScanner(rootLog)\n\t\tfor scn.Scan() && !levelSet {\n\t\t\tline := scn.Text()\n\t\t\tlevelSet = strings.Contains(line, `Logging at this level \"TRACE\"`)\n\t\t\tpos += int64(len(line)) + 1\n\t\t}\n\t\treturn levelSet\n\t}, 60*time.Second, 5*time.Second, \"Root log level not updated in 20 seconds\")\n\n\t// Make sure the log level was set back after disconnect\n\ts.Eventually(func() bool {\n\t\trootLog, err := os.Open(rootLogName)\n\t\trequire.NoError(err)\n\t\tdefer rootLog.Close()\n\t\t_, err = rootLog.Seek(pos, 0)\n\t\trequire.NoError(err)\n\n\t\tlevelSet := false\n\t\tscn := bufio.NewScanner(rootLog)\n\t\tfor scn.Scan() && !levelSet {\n\t\t\tline := scn.Text()\n\t\t\tlevelSet = strings.Contains(line, `Logging at this level \"INFO\"`)\n\t\t\tpos += int64(len(line)) + 1\n\t\t}\n\t\treturn levelSet\n\t}, 5*time.Second, time.Second, \"Root log level not reset after disconnect\")\n\n\t// Set it to a \"real\" value to see that the client-side wins\n\tctx = itest.WithConfig(ctx, func(config client.Config) {\n\t\tconfig.LogLevels().RootDaemon = slog.LevelDebug\n\t})\n\n\ts.TelepresenceConnect(ctx)\n\trootLog, err := os.Open(rootLogName)\n\trequire.NoError(err)\n\tdefer rootLog.Close()\n\t_, err = rootLog.Seek(pos, 0)\n\trequire.NoError(err)\n\n\tlevelSet := false\n\tscn := bufio.NewScanner(rootLog)\n\tfor scn.Scan() && !levelSet {\n\t\tlevelSet = strings.Contains(scn.Text(), `Logging at this level \"TRACE\"`)\n\t}\n\titest.TelepresenceDisconnectOk(ctx)\n\trequire.False(levelSet, \"Root log level not respected when set in config file\")\n\tview, err := s.configView()\n\trequire.NoError(err)\n\trequire.Equal(view.LogLevels().RootDaemon, slog.LevelDebug)\n}\n\nfunc (s *notConnectedSuite) Test_UserdCloudLogLevel() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\tuserLogName := filepath.Join(filelocation.AppUserLogDir(ctx), \"connector.log\")\n\t// Figure out where the current end of the logfile is.\n\tpos := int64(0)\n\tst, err := os.Stat(userLogName)\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\ts.T().Fatalf(\"Unexpected error stat'ing %s: %v\", userLogName, err)\n\t\t}\n\t} else {\n\t\tpos = st.Size()\n\t}\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"logLevel=debug,agent.logLevel=debug,client.logLevels.userDaemon=trace\")\n\tdefer s.RollbackTM(ctx)\n\tctx = itest.WithConfig(ctx, func(cfg client.Config) {\n\t\tcfg.LogLevels().UserDaemon = slog.LevelInfo\n\t})\n\n\ts.Eventually(func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"connect\", \"--manager-namespace\", s.ManagerNamespace(), \"--namespace\", s.AppNamespace())\n\t\tclog.Infof(ctx, \"stdout %s\", so)\n\t\tclog.Infof(ctx, \"stderr %s\", se)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\titest.TelepresenceDisconnectOk(ctx)\n\n\t\tlogF, err := os.Open(userLogName)\n\t\trequire.NoError(err)\n\t\tdefer logF.Close()\n\t\t_, err = logF.Seek(pos, 0)\n\t\trequire.NoError(err)\n\n\t\tscn := bufio.NewScanner(logF)\n\t\tlevelSet := false\n\t\tfor scn.Scan() && !levelSet {\n\t\t\tline := scn.Text()\n\t\t\tlevelSet = strings.Contains(line, `Logging at this level \"TRACE\"`)\n\t\t\tpos += int64(len(line)) + 1\n\t\t}\n\t\treturn levelSet\n\t}, 20*time.Second, 5*time.Second, \"Connector log level not updated in 20 seconds\")\n\n\t// Make sure the log level was set back after disconnect\n\tlogF, err := os.Open(userLogName)\n\trequire.NoError(err)\n\t_, err = logF.Seek(pos, 0)\n\tif !s.NoError(err) {\n\t\tlogF.Close()\n\t\treturn\n\t}\n\n\tscn := bufio.NewScanner(logF)\n\tlevelSet := false\n\tfor scn.Scan() && !levelSet {\n\t\tlevelSet = strings.Contains(scn.Text(), `Logging at this level \"INFO\"`)\n\t}\n\tlogF.Close()\n\trequire.True(levelSet, \"Connector log level not reset after disconnect\")\n\n\t// Set it to a \"real\" value to see that the client-side wins\n\tctx = itest.WithConfig(ctx, func(config client.Config) {\n\t\tconfig.LogLevels().UserDaemon = slog.LevelDebug\n\t})\n\n\ts.TelepresenceConnect(ctx)\n\titest.TelepresenceDisconnectOk(ctx)\n\n\tlogF, err = os.Open(userLogName)\n\trequire.NoError(err)\n\t_, err = logF.Seek(pos, 0)\n\tif !s.NoError(err) {\n\t\tlogF.Close()\n\t\treturn\n\t}\n\n\tlevelSet = false\n\tfor scn.Scan() && !levelSet {\n\t\tlevelSet = strings.Contains(scn.Text(), `Logging at this level \"TRACE\"`)\n\t}\n\tlogF.Close()\n\trequire.False(levelSet, \"Connector log level not respected when set in config file\")\n}\n"
  },
  {
    "path": "integration_test/compose_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\tgoRuntime \"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\ntype composeSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tctx context.Context\n}\n\nfunc (s *composeSuite) SuiteName() string {\n\treturn \"Compose\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &composeSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *composeSuite) SetupSuite() {\n\tif s.IsCI() && !(goRuntime.GOOS == \"linux\" && goRuntime.GOARCH == \"amd64\") {\n\t\ts.T().Skip(\"CI can't run linux docker containers inside non-linux runners\")\n\t\treturn\n\t}\n\ts.Suite.SetupSuite()\n\ts.ctx = itest.WithConfig(s.HarnessContext(), func(cfg client.Config) {\n\t\tcfg.Intercept().UseFtp = false\n\t})\n}\n\nfunc (s *composeSuite) TearDownTest() {\n\titest.TelepresenceQuitOk(s.Context())\n}\n\nfunc (s *composeSuite) Context() context.Context {\n\treturn itest.WithT(s.ctx, s.T())\n}\n\nfunc (s *composeSuite) Test_ComposeDNS() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\tconst svc = \"echo-easy\"\n\ts.ApplyEchoService(ctx, svc, 80)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\tconst svc2 = \"echo-other\"\n\ts.ApplyEchoService(ctx, svc2, 80)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc2)\n\n\t// Write a docker-compose.yml that replaces echo-other and sleeps.\n\tcomposeDir := itest.TempDir(ctx)\n\tcomposeFile := filepath.Join(composeDir, \"docker-compose.yml\")\n\tns := s.AppNamespace()\n\tcomposeContent := strings.Join([]string{\n\t\t\"x-tele:\",\n\t\t\"  connections:\",\n\t\t\"    - namespace: \" + ns,\n\t\t\"      manager-namespace: \" + s.ManagerNamespace(),\n\t\t\"services:\",\n\t\t\"  \" + svc2 + \":\",\n\t\t\"    x-tele:\",\n\t\t\"      type: replace\",\n\t\t\"    image: busybox\",\n\t\t\"    command: sleep infinity\",\n\t}, \"\\n\")\n\trequire.NoError(os.WriteFile(composeFile, []byte(composeContent), 0o644))\n\n\t_, _, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"up\", \"-d\")\n\trequire.NoError(err, \"compose up failed\")\n\tdefer func() {\n\t\t_, _, _ = itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"down\")\n\t}()\n\n\t// Verify that cluster DNS resolves inside the compose container.\n\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"exec\", svc2, \"nslookup\", svc)\n\t\tif err == nil && strings.Contains(so, \"Address:\") {\n\t\t\treturn true\n\t\t}\n\t\tclog.Info(ctx, \"nslookup\", \"sdtout\", so, \"stderr\", se, \"err\", err)\n\t\treturn false\n\t}, 30*time.Second, 3*time.Second, \"nslookup of %s in compose container should succeed\", svc)\n}\n\nfunc (s *composeSuite) Test_ComposeConnect() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\tconst svc = \"echo-easy\"\n\ts.ApplyEchoService(ctx, svc, 80)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\tcomposeDir := itest.TempDir(ctx)\n\tcomposeFile := filepath.Join(composeDir, \"docker-compose.yml\")\n\tns := s.AppNamespace()\n\tcomposeContent := strings.Join([]string{\n\t\t\"x-tele:\",\n\t\t\"  connections:\",\n\t\t\"    - namespace: \" + ns,\n\t\t\"      manager-namespace: \" + s.ManagerNamespace(),\n\t\t\"services:\",\n\t\t\"  tester:\",\n\t\t\"    x-tele:\",\n\t\t\"      type: connect\",\n\t\t\"    image: busybox\",\n\t\t\"    command: sleep infinity\",\n\t}, \"\\n\")\n\trequire.NoError(os.WriteFile(composeFile, []byte(composeContent), 0o644))\n\n\t_, _, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"up\", \"-d\")\n\trequire.NoError(err, \"compose up failed\")\n\tdefer func() {\n\t\t_, _, _ = itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"down\")\n\t}()\n\n\t// Verify HTTP access to a cluster service from the connect container using its single-label name.\n\t// The tel2-search DNS search domain causes the resolver to first try \"echo-easy.tel2-search\",\n\t// which Docker's embedded DNS forwards to the Telepresence daemon DNS. The daemon strips the\n\t// tel2-search suffix and resolves the bare name against the cluster.\n\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"exec\", \"tester\", \"wget\", \"-qO-\", \"http://\"+svc)\n\t\tif err == nil && len(so) > 0 {\n\t\t\treturn true\n\t\t}\n\t\tclog.Info(ctx, \"wget\", \"stdout\", so, \"stderr\", se, \"err\", err)\n\t\treturn false\n\t}, 30*time.Second, 3*time.Second, \"wget of %s from connect container should succeed\", svc)\n}\n\nfunc (s *composeSuite) Test_ComposeProxy() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\tconst svc = \"echo-easy\"\n\ts.ApplyEchoService(ctx, svc, 80)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\tcomposeDir := itest.TempDir(ctx)\n\tcomposeFile := filepath.Join(composeDir, \"docker-compose.yml\")\n\tns := s.AppNamespace()\n\tcomposeContent := strings.Join([]string{\n\t\t\"x-tele:\",\n\t\t\"  connections:\",\n\t\t\"    - namespace: \" + ns,\n\t\t\"      manager-namespace: \" + s.ManagerNamespace(),\n\t\t\"services:\",\n\t\t\"  \" + svc + \":\",\n\t\t\"    x-tele:\",\n\t\t\"      type: proxy\",\n\t\t\"  tester:\",\n\t\t\"    x-tele:\",\n\t\t\"      type: connect\",\n\t\t\"    image: busybox\",\n\t\t\"    command: sleep infinity\",\n\t}, \"\\n\")\n\trequire.NoError(os.WriteFile(composeFile, []byte(composeContent), 0o644))\n\n\t_, _, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"up\", \"-d\")\n\trequire.NoError(err, \"compose up failed\")\n\tdefer func() {\n\t\t_, _, _ = itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"down\")\n\t}()\n\n\t// Verify that HTTP requests are routed through the proxy to the cluster service.\n\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"exec\", \"tester\", \"wget\", \"-qO-\", \"http://\"+svc)\n\t\tif err == nil && len(so) > 0 {\n\t\t\treturn true\n\t\t}\n\t\tclog.Info(ctx, \"wget via proxy\", \"stdout\", so, \"stderr\", se, \"err\", err)\n\t\treturn false\n\t}, 30*time.Second, 3*time.Second, \"wget of %s through proxy should succeed\", svc)\n}\n\nfunc (s *composeSuite) Test_ComposeIngest() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\tconst ingestSvc = \"ingest-app\"\n\titest.ApplyAppTemplate(ctx, s.AppNamespace(), &itest.AppData{\n\t\tAppName: ingestSvc,\n\t\tImage:   \"ghcr.io/telepresenceio/echo-server:0.3.1\",\n\t\tPorts: []itest.AppPort{\n\t\t\t{ServicePortNumber: 80, TargetPortNumber: 8080},\n\t\t},\n\t\tEnv: map[string]string{\n\t\t\t\"INGEST_TEST_VAR\": \"hello-from-cluster\",\n\t\t},\n\t})\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", ingestSvc)\n\n\tcomposeDir := itest.TempDir(ctx)\n\tcomposeFile := filepath.Join(composeDir, \"docker-compose.yml\")\n\tns := s.AppNamespace()\n\tcomposeContent := strings.Join([]string{\n\t\t\"x-tele:\",\n\t\t\"  connections:\",\n\t\t\"    - namespace: \" + ns,\n\t\t\"      manager-namespace: \" + s.ManagerNamespace(),\n\t\t\"services:\",\n\t\t\"  \" + ingestSvc + \":\",\n\t\t\"    x-tele:\",\n\t\t\"      type: ingest\",\n\t\t\"    image: busybox\",\n\t\t\"    command: sleep infinity\",\n\t}, \"\\n\")\n\trequire.NoError(os.WriteFile(composeFile, []byte(composeContent), 0o644))\n\n\t_, _, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"up\", \"-d\")\n\trequire.NoError(err, \"compose up failed\")\n\tdefer func() {\n\t\t_, _, _ = itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"down\")\n\t}()\n\n\t// Verify that the ingest container inherits the environment variable from the cluster workload.\n\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"exec\", ingestSvc, \"env\")\n\t\tif err == nil && strings.Contains(so, \"INGEST_TEST_VAR=hello-from-cluster\") {\n\t\t\treturn true\n\t\t}\n\t\tclog.Info(ctx, \"env check\", \"stdout\", so, \"stderr\", se, \"err\", err)\n\t\treturn false\n\t}, 60*time.Second, 5*time.Second, \"ingest container should inherit INGEST_TEST_VAR from cluster workload\")\n}\n\nfunc (s *composeSuite) Test_ComposeIntercept() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\tconst svc = \"echo-easy\"\n\ts.ApplyEchoService(ctx, svc, 80)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\tcomposeDir := itest.TempDir(ctx)\n\tcomposeFile := filepath.Join(composeDir, \"docker-compose.yml\")\n\tns := s.AppNamespace()\n\tcomposeContent := strings.Join([]string{\n\t\t\"x-tele:\",\n\t\t\"  connections:\",\n\t\t\"    - namespace: \" + ns,\n\t\t\"      manager-namespace: \" + s.ManagerNamespace(),\n\t\t\"services:\",\n\t\t\"  \" + svc + \":\",\n\t\t\"    x-tele:\",\n\t\t\"      type: intercept\",\n\t\t\"      ports:\",\n\t\t`        - \"80:80\"`,\n\t\t\"    image: hashicorp/http-echo\",\n\t\t`    command: [\"-text=hello-from-compose\", \"-listen=:80\"]`,\n\t\t\"  tester:\",\n\t\t\"    x-tele:\",\n\t\t\"      type: connect\",\n\t\t\"    image: busybox\",\n\t\t\"    command: sleep infinity\",\n\t}, \"\\n\")\n\trequire.NoError(os.WriteFile(composeFile, []byte(composeContent), 0o644))\n\n\t_, _, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"up\", \"-d\")\n\trequire.NoError(err, \"compose up failed\")\n\tdefer func() {\n\t\t_, _, _ = itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"down\")\n\t}()\n\n\t// Verify that cluster traffic is intercepted and served by the local compose container.\n\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"exec\", \"tester\", \"wget\", \"-qO-\", \"http://\"+svc)\n\t\tif err == nil && strings.Contains(so, \"hello-from-compose\") {\n\t\t\treturn true\n\t\t}\n\t\tclog.Info(ctx, \"wget intercept\", \"stdout\", so, \"stderr\", se, \"err\", err)\n\t\treturn false\n\t}, 60*time.Second, 5*time.Second, \"intercept should redirect cluster traffic to the compose container\")\n}\n\nfunc (s *composeSuite) Test_ComposeReplace() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\tconst svc = \"echo-easy\"\n\ts.ApplyEchoService(ctx, svc, 80)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\tcomposeDir := itest.TempDir(ctx)\n\tcomposeFile := filepath.Join(composeDir, \"docker-compose.yml\")\n\tns := s.AppNamespace()\n\tcomposeContent := strings.Join([]string{\n\t\t\"x-tele:\",\n\t\t\"  connections:\",\n\t\t\"    - namespace: \" + ns,\n\t\t\"      manager-namespace: \" + s.ManagerNamespace(),\n\t\t\"services:\",\n\t\t\"  \" + svc + \":\",\n\t\t\"    x-tele:\",\n\t\t\"      type: replace\",\n\t\t\"      ports:\",\n\t\t`        - \"80:8080\"`, // local port 80 -> cluster container port 8080 (echo-server listens on 8080)\n\t\t\"    image: hashicorp/http-echo\",\n\t\t`    command: [\"-text=hello-from-compose\", \"-listen=:80\"]`,\n\t\t\"  tester:\",\n\t\t\"    x-tele:\",\n\t\t\"      type: connect\",\n\t\t\"    image: busybox\",\n\t\t\"    command: sleep infinity\",\n\t}, \"\\n\")\n\trequire.NoError(os.WriteFile(composeFile, []byte(composeContent), 0o644))\n\n\t_, _, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"up\", \"-d\")\n\trequire.NoError(err, \"compose up failed\")\n\tdefer func() {\n\t\t_, _, _ = itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"down\")\n\t}()\n\n\t// Verify that the local compose container serves traffic in place of the cluster pod.\n\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"exec\", \"tester\", \"wget\", \"-qO-\", \"http://\"+svc)\n\t\tif err == nil && strings.Contains(so, \"hello-from-compose\") {\n\t\t\treturn true\n\t\t}\n\t\tclog.Info(ctx, \"wget replace\", \"stdout\", so, \"stderr\", se, \"err\", err)\n\t\treturn false\n\t}, 60*time.Second, 5*time.Second, \"replace should serve traffic from the compose container instead of the cluster pod\")\n}\n\nfunc (s *composeSuite) Test_ComposeWiretap() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\tconst svc = \"echo-easy\"\n\ts.ApplyEchoService(ctx, svc, 80)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\tcomposeDir := itest.TempDir(ctx)\n\tcomposeFile := filepath.Join(composeDir, \"docker-compose.yml\")\n\tns := s.AppNamespace()\n\tcomposeContent := strings.Join([]string{\n\t\t\"x-tele:\",\n\t\t\"  connections:\",\n\t\t\"    - namespace: \" + ns,\n\t\t\"      manager-namespace: \" + s.ManagerNamespace(),\n\t\t\"services:\",\n\t\t\"  \" + svc + \":\",\n\t\t\"    x-tele:\",\n\t\t\"      type: wiretap\",\n\t\t\"      ports:\",\n\t\t`        - \"80:80\"`,\n\t\t\"    image: hashicorp/http-echo\",\n\t\t`    command: [\"-text=hello-from-compose\", \"-listen=:80\"]`,\n\t\t\"  tester:\",\n\t\t\"    x-tele:\",\n\t\t\"      type: connect\",\n\t\t\"    image: busybox\",\n\t\t\"    command: sleep infinity\",\n\t}, \"\\n\")\n\trequire.NoError(os.WriteFile(composeFile, []byte(composeContent), 0o644))\n\n\t_, _, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"up\", \"-d\")\n\trequire.NoError(err, \"compose up failed\")\n\tdefer func() {\n\t\t_, _, _ = itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"down\")\n\t}()\n\n\t// Use the namespace-qualified name to reach the cluster service rather than the local compose\n\t// container. Docker's embedded DNS resolves bare \"echo-easy\" to the local wiretap-receiver\n\t// container, which would bypass the cluster and never trigger the traffic-agent. A\n\t// namespace-qualified name is not known to Docker's embedded DNS, so it is forwarded to the\n\t// Telepresence daemon DNS which routes it to the cluster.\n\tclusterSvcName := svc + \".\" + ns\n\n\t// Verify that the cluster service still serves the original traffic (not the local container).\n\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"exec\", \"tester\", \"wget\", \"-qO-\", \"http://\"+clusterSvcName)\n\t\tif err == nil && len(so) > 0 && !strings.Contains(so, \"hello-from-compose\") {\n\t\t\treturn true\n\t\t}\n\t\tclog.Info(ctx, \"wget wiretap cluster check\", \"stdout\", so, \"stderr\", se, \"err\", err)\n\t\treturn false\n\t}, 60*time.Second, 5*time.Second, \"cluster service should still serve original traffic during wiretap\")\n\n\t// Make several requests via the namespace-qualified name so the traffic-agent wiretap fires\n\t// and sends copies to the local container.\n\tfor range 3 {\n\t\t_, _, _ = itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"exec\", \"tester\", \"wget\", \"-qO-\", \"http://\"+clusterSvcName)\n\t}\n\n\t// Verify that the wiretap container received copies of the traffic via its logs.\n\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\tso, se, err := itest.Telepresence(ctx, \"compose\", \"-f\", composeFile, \"logs\", svc)\n\t\tif err == nil && strings.Contains(so, \"GET\") {\n\t\t\treturn true\n\t\t}\n\t\tclog.Info(ctx, \"compose logs wiretap\", \"stdout\", so, \"stderr\", se, \"err\", err)\n\t\treturn false\n\t}, 30*time.Second, 3*time.Second, \"wiretap container should receive copies of traffic\")\n}\n"
  },
  {
    "path": "integration_test/config_test.go",
    "content": "package integration_test\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc (s *notConnectedSuite) Test_EmptyConfigFile() {\n\tctx := s.Context()\n\tcfgDir := itest.TempDir(ctx)\n\tctx = filelocation.WithAppUserConfigDir(ctx, cfgDir)\n\tf, err := os.Create(filepath.Join(cfgDir, \"config.yml\"))\n\ts.Require().NoError(err)\n\tf.Close()\n\ts.TelepresenceConnect(ctx)\n\titest.TelepresenceQuitOk(ctx)\n}\n"
  },
  {
    "path": "integration_test/connected_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n)\n\ntype connectedSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *connectedSuite) SuiteName() string {\n\treturn \"Connected\"\n}\n\nfunc init() {\n\titest.AddConnectedSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &connectedSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *connectedSuite) Test_ListExcludesTM() {\n\tstdout := itest.TelepresenceOk(s.Context(), \"list\", \"-n\", s.ManagerNamespace())\n\ts.NotContains(stdout, agentconfig.ManagerAppName)\n}\n\nfunc (s *connectedSuite) Test_ReportsAllVersions() {\n\tstdout := itest.TelepresenceOk(s.Context(), \"version\")\n\trxVer := regexp.QuoteMeta(s.ClientVersion().String())\n\ts.Regexp(fmt.Sprintf(`Client\\s*: v%s`, rxVer), stdout)\n\ts.Regexp(fmt.Sprintf(`Root Daemon\\s*: v%s`, rxVer), stdout)\n\ts.Regexp(fmt.Sprintf(`User Daemon\\s*: v%s`, rxVer), stdout)\n\tmgrVer := regexp.QuoteMeta(s.ManagerVersion().String())\n\ts.Regexp(fmt.Sprintf(`Traffic Manager\\s*: v%s`, mgrVer), stdout)\n}\n\nfunc (s *connectedSuite) Test_Status() {\n\tstdout := itest.TelepresenceOk(s.Context(), \"status\")\n\ts.Contains(stdout, \"Root Daemon: Running\")\n\ts.Contains(stdout, \"User Daemon: Running\")\n\ts.Contains(stdout, \"Kubernetes context:\")\n\ts.Regexp(`Manager namespace\\s+: `+s.ManagerNamespace(), stdout)\n}\n\nfunc (s *connectedSuite) Test_StatusWithJSON() {\n\tstatus := itest.TelepresenceStatusOk(s.Context())\n\ts.True(status.RootDaemon.Running)\n\ts.True(status.UserDaemon.Running)\n\ts.NotEmpty(status.UserDaemon.KubernetesContext)\n\ts.NotEmpty(status.UserDaemon.InstallID)\n\ts.Equal(status.UserDaemon.ManagerNamespace, s.ManagerNamespace())\n\ts.Require().NotNil(status.TrafficManager)\n\ts.NotEmpty(status.TrafficManager.Version)\n\ts.NotEmpty(status.TrafficManager.TrafficAgent)\n}\n"
  },
  {
    "path": "integration_test/container_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n)\n\nfunc (s *connectedSuite) Test_InterceptsContainer() {\n\tctx, cancel := context.WithCancel(s.Context())\n\tdefer cancel()\n\tconst svc = \"echo-secondary\"\n\n\tsvcPort, svcCancel := itest.StartLocalHttpEchoServer(ctx, svc)\n\tdefer svcCancel()\n\n\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deployment\", svc)\n\tdefer func() {\n\t\ts.NoError(s.Kubectl(ctx, \"delete\", \"configmaps\", \"socat-data\", \"echo-data\"))\n\t}()\n\n\trequire := s.Require()\n\tdir := s.T().TempDir()\n\tenvFile := filepath.Join(dir, \"env.json\")\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", svc,\n\t\t\"--output\", \"json\",\n\t\t\"--detailed-output\",\n\t\t\"--container\", \"echo\",\n\t\t\"--env-json\", envFile,\n\t\t\"--port\", strconv.Itoa(svcPort))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", svc)\n\n\tvar iInfo intercept.Info\n\trequire.NoError(json.Unmarshal([]byte(stdout), &iInfo))\n\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\treturn err == nil && strings.Contains(stdout, svc+\": intercepted\")\n\t\t},\n\t\t30*time.Second, // waitFor\n\t\t3*time.Second,  // polling interval\n\t\t`intercepted workload never show up in list`)\n\n\titest.PingInterceptedEchoServer(ctx, svc, \"80\")\n\n\t// Check that the env stems from the targeted container\n\ts.Equal(\"echo-server\", iInfo.Environment[\"TAG\"])\n\tmountPoint := iInfo.Mount.LocalDir\n\tdataDir := filepath.Join(mountPoint, \"usr\", \"share\", \"data\")\n\tst, err := os.Stat(dataDir)\n\trequire.NoError(err, \"mount of %s should be successful\", dataDir)\n\trequire.True(st.IsDir())\n\tdataFile := filepath.Join(dataDir, \"text\")\n\tcontent, err := os.ReadFile(dataFile)\n\trequire.NoError(err, \"unable to read\", dataFile)\n\ts.Equal(\"Hello from echo\\n\", string(content))\n\n\t// Intercept again, this time without the --container flag\n\titest.TelepresenceOk(ctx, \"leave\", svc)\n\tstdout = itest.TelepresenceOk(ctx, \"intercept\", svc,\n\t\t\"--output\", \"json\",\n\t\t\"--detailed-output\",\n\t\t\"--env-json\", envFile,\n\t\t\"--port\", strconv.Itoa(svcPort))\n\n\tiInfo = intercept.Info{}\n\trequire.NoError(json.Unmarshal([]byte(stdout), &iInfo))\n\ts.Equal(iInfo.Environment[\"TAG\"], \"socat\")\n\n\titest.PingInterceptedEchoServer(ctx, svc, \"80\")\n\tmountPoint = iInfo.Mount.LocalDir\n\tdataFile = filepath.Join(mountPoint, \"usr\", \"share\", \"data\", \"text\")\n\tcontent, err = os.ReadFile(dataFile)\n\trequire.NoError(err, \"unable to read\", dataFile)\n\ts.Equal(\"Hello from socat\\n\", string(content))\n}\n\nfunc (s *connectedSuite) Test_InterceptsContainerAndReplace() {\n\tctx, cancel := context.WithCancel(s.Context())\n\tdefer cancel()\n\tconst svc = \"echo-secondary\"\n\n\tsvcPort, svcCancel := itest.StartLocalHttpEchoServer(ctx, svc)\n\tdefer svcCancel()\n\n\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deployment\", svc)\n\tdefer func() {\n\t\ts.NoError(s.Kubectl(ctx, \"delete\", \"configmaps\", \"socat-data\", \"echo-data\"))\n\t}()\n\n\trequire := s.Require()\n\tdir := s.T().TempDir()\n\tenvFile := filepath.Join(dir, \"env.json\")\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", svc,\n\t\t\"--output\", \"json\",\n\t\t\"--detailed-output\",\n\t\t\"--container\", \"echo\",\n\t\t\"--replace\",\n\t\t\"--env-json\", envFile,\n\t\t\"--port\", strconv.Itoa(svcPort))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", svc)\n\n\tvar iInfo intercept.Info\n\trequire.NoError(json.Unmarshal([]byte(stdout), &iInfo))\n\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\titest.PingInterceptedEchoServer(ctx, svc, \"80\")\n\n\t// Check that the env stems from the targeted container\n\ts.Equal(iInfo.Environment[\"TAG\"], \"echo-server\")\n\tmountPoint := iInfo.Mount.LocalDir\n\tdataDir := filepath.Join(mountPoint, \"usr\", \"share\", \"data\")\n\tst, err := os.Stat(dataDir)\n\trequire.NoError(err, \"mount of %s should be successful\", dataDir)\n\trequire.True(st.IsDir())\n\tdataFile := filepath.Join(dataDir, \"text\")\n\tcontent, err := os.ReadFile(dataFile)\n\trequire.NoErrorf(err, \"unable to read %s\", dataFile)\n\ts.Equal(\"Hello from echo\\n\", string(content))\n\n\t// Verify that the container is replaced.\n\tstdout, err = s.KubectlOut(ctx, \"get\", \"pod\", \"-l\", \"app=\"+svc, \"-o\", \"json\")\n\trequire.NoError(err)\n\titems := struct {\n\t\tApiVersion string     `json:\"apiVersion\"`\n\t\tItems      []core.Pod `json:\"items\"`\n\t}{}\n\trequire.NoError(json.Unmarshal([]byte(stdout), &items))\n\tpods := items.Items\n\tvar echoContainer *core.Container\n\tfor pi := range pods {\n\t\tcns := pods[pi].Spec.Containers\n\t\tfor ci := range cns {\n\t\t\tcontainer := &cns[ci]\n\t\t\tif container.Name == \"echo\" {\n\t\t\t\techoContainer = container\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif s.ManagerIsVersion(\">2.21.x\") {\n\t\trequire.Nil(echoContainer)\n\t} else {\n\t\trequire.NotNil(echoContainer)\n\t\trequire.Equal(echoContainer.Image, \"alpine:latest\")\n\t\trequire.True(slices.Equal(echoContainer.Args, []string{\"sleep\", \"infinity\"}))\n\t}\n\n\t// Intercept again, this time without the --container flag\n\titest.TelepresenceOk(ctx, \"leave\", svc)\n\tstdout = itest.TelepresenceOk(ctx, \"intercept\", svc,\n\t\t\"--output\", \"json\",\n\t\t\"--detailed-output\",\n\t\t\"--env-json\", envFile,\n\t\t\"--port\", strconv.Itoa(svcPort))\n\n\tiInfo = intercept.Info{}\n\trequire.NoError(json.Unmarshal([]byte(stdout), &iInfo))\n\ts.Equal(iInfo.Environment[\"TAG\"], \"socat\")\n\n\titest.PingInterceptedEchoServer(ctx, svc, \"80\")\n\tmountPoint = iInfo.Mount.LocalDir\n\tdataFile = filepath.Join(mountPoint, \"usr\", \"share\", \"data\", \"text\")\n\tcontent, err = os.ReadFile(dataFile)\n\trequire.NoError(err, \"unable to read\", dataFile)\n\ts.Equal(\"Hello from socat\\n\", string(content))\n}\n"
  },
  {
    "path": "integration_test/docker_daemon_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\tgoRuntime \"runtime\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\ntype dockerDaemonSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tctx context.Context\n}\n\nfunc (s *dockerDaemonSuite) SuiteName() string {\n\treturn \"DockerDaemon\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &dockerDaemonSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *dockerDaemonSuite) SetupSuite() {\n\tif s.IsCI() && !(goRuntime.GOOS == \"linux\" && goRuntime.GOARCH == \"amd64\") {\n\t\ts.T().Skip(\"CI can't run linux docker containers inside non-linux runners\")\n\t\treturn\n\t}\n\ts.Suite.SetupSuite()\n\ts.ctx = itest.WithConfig(s.HarnessContext(), func(cfg client.Config) {\n\t\tcfg.Intercept().UseFtp = false\n\t})\n}\n\nfunc (s *dockerDaemonSuite) TearDownTest() {\n\titest.TelepresenceQuitOk(s.Context())\n}\n\nfunc (s *dockerDaemonSuite) Context() context.Context {\n\treturn itest.WithT(s.ctx, s.T())\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerDaemon_status() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx, \"--docker\")\n\n\tstatus := itest.TelepresenceStatusOk(ctx)\n\tud := status.UserDaemon\n\ts.True(ud.Running)\n\ts.True(strings.HasSuffix(ud.Name, s.AppNamespace()+\"-cn\"), \"ends with suffix <namespace>-cn\")\n\ts.Equal(ud.Status, \"Connected\")\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerDaemon_hostDaemonNoConflict() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\t_, _, err := itest.Telepresence(ctx, \"connect\", \"--docker\", \"--namespace\", s.AppNamespace(), \"--manager-namespace\", s.ManagerNamespace())\n\ts.NoError(err)\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerDaemon_alsoProxy32() {\n\tconst ipToTest = \"10.10.74.1\"\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--also-proxy\", ipToTest+\"/32\", \"--name\", \"ax\")\n\titest.TelepresenceOk(ctx, \"loglevel\", \"trace\")\n\tdefer itest.TelepresenceOk(ctx, \"loglevel\", \"debug\")\n\n\trq := s.Require()\n\tlogFile := filepath.Join(filelocation.AppUserLogDir(s.Context()), \"connector.log\")\n\trootLog, err := os.Open(logFile)\n\trq.NoError(err)\n\tdefer rootLog.Close()\n\n\t// Figure out where the current end of the logfile is. This must be done before any\n\t// of the tests run because the queries that the DNS resolver receives are dependent\n\t// on how the system's DNS resolver handles search paths and caching.\n\tst, err := rootLog.Stat()\n\trq.NoError(err)\n\tpos := st.Size()\n\n\t// Make an attempt to curl the also-proxied IP. The attempt will fail (there's nothing at the\n\t// other end), and that's OK. We're just interested in seeing it logged.\n\t_, _, _ = itest.Telepresence(ctx, \"curl\", \"--silent\", \"--max-time\", \"1\", ipToTest) //nolint:dogsled // X\n\n\t// Verify that the attempt is visible in the root log.\n\t_, err = rootLog.Seek(pos, io.SeekStart)\n\trq.NoError(err)\n\tscn := bufio.NewScanner(rootLog)\n\tfound := false\n\n\t// mustHaveWanted caters for cases where the default behavior from the system's resolver\n\t// is to not send unwanted queries to our resolver at all (based on search and routes).\n\t// It is forced to true for inclusion tests.\n\tstrToFind := fmt.Sprintf(\"%s:80, code STREAM_INFO\", ipToTest)\n\tfor scn.Scan() {\n\t\ttxt := scn.Text()\n\t\tif strings.Contains(txt, strToFind) {\n\t\t\tfound = true\n\t\t\tbreak\n\t\t}\n\t}\n\ts.Truef(found, \"Unable to find %q\", strToFind)\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerDaemon_daemonHostNotConflict() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx, \"--docker\")\n\ts.TelepresenceConnect(ctx)\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerDaemon_singleNameLookup() {\n\tctx := s.Context()\n\tconst svc = \"echo-easy\"\n\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\tout := s.TelepresenceConnect(ctx, \"--docker\", \"--\", itest.GetExecutable(ctx), \"curl\", \"--silent\", \"--max-time\", \"1\", svc)\n\ts.Contains(out, \"Request served by \"+svc)\n\tso, err := itest.TelepresenceStatus(ctx)\n\ts.NoError(err)\n\ts.Nil(so.ContainerizedDaemon)\n\ts.False(so.UserDaemon.Running)\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerDaemon_cacheFiles() {\n\tctx := s.Context()\n\trq := s.Require()\n\tcache := filelocation.AppUserCacheDir(ctx)\n\n\t// Create a random file, just to get a dos-file handle with our own UID/GID\n\trf, err := dos.Create(ctx, filepath.Join(s.T().TempDir(), \"random.file\"))\n\trq.NoError(err)\n\trs, err := logging.FStat(rf)\n\t_ = rf.Close()\n\trq.NoError(err)\n\n\tlv := filepath.Join(cache, client.UserDaemonName+\".loglevel\")\n\tctx = dos.WithLockedFs(ctx)\n\t_ = dos.Remove(ctx, lv)\n\ts.TelepresenceConnect(ctx, \"--docker\")\n\titest.TelepresenceOk(ctx, \"loglevel\", \"trace\")\n\tdefer itest.TelepresenceOk(ctx, \"loglevel\", \"debug\")\n\tdf, err := dos.Open(ctx, lv)\n\trq.NoError(err)\n\tst, err := logging.FStat(df)\n\t_ = df.Close()\n\trq.NoError(err)\n\trq.True(st.HaveSameOwnerAndGroup(rs))\n}\n\nfunc (s *dockerDaemonSuite) Test_GatherLogsTrafficManager() {\n\tctx := s.Context()\n\toutputDir := itest.TempDir(ctx)\n\toutputFile := filepath.Join(outputDir, \"allLogs.zip\")\n\ts.TelepresenceConnect(ctx, \"--docker\")\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--daemons\", \"None\", \"--traffic-agents\", \"None\", \"--output-file\", outputFile)\n\tfoundManager, _, _, fileNames := getZipData(s.Require(), outputFile, s.AppNamespace(), s.ManagerNamespace(), \"echo-easy\")\n\ts.Require().True(foundManager)\n\ts.Require().True(slices.ContainsFunc(fileNames, func(name string) bool { return strings.HasPrefix(name, \"traffic-manager\") }))\n}\n"
  },
  {
    "path": "integration_test/docker_run_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\tgoRuntime \"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\nfunc runDockerRun(ctx context.Context, name, svc, port, appDir, tag string, rq *itest.Requirements, wch chan<- struct{}) *os.Process {\n\t_ = itest.Run(ctx, \"docker\", \"container\", \"stop\", name)\n\targs := []string{\"intercept\", \"--mount\", \"false\", svc, \"--docker-run\"}\n\tif port != \"\" {\n\t\targs = append(args, \"--port\", port)\n\t}\n\targs = append(args, \"--\", \"--rm\", \"-v\", appDir+\":/usr/src/app\")\n\tif name != \"\" {\n\t\targs = append(args, \"--name\", name)\n\t}\n\targs = append(args, tag)\n\tcmd := itest.TelepresenceCmd(ctx, args...)\n\tso := &bytes.Buffer{}\n\tse := &bytes.Buffer{}\n\tcmd.Stdout = so\n\tcmd.Stderr = se\n\trq.NoError(cmd.Start())\n\tproc := cmd.Process\n\tgo func() {\n\t\tif wch != nil {\n\t\t\tdefer close(wch)\n\t\t}\n\t\terr := cmd.Wait()\n\t\tclog.Info(ctx, so.String())\n\t\tif ses := se.String(); ses != \"\" {\n\t\t\tclog.Error(ctx, ses)\n\t\t}\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err.Error())\n\t\t}\n\t}()\n\treturn proc\n}\n\nfunc (s *singleServiceSuite) Test_DockerRun_HostDaemon() {\n\tif s.IsCI() && !(goRuntime.GOOS == \"linux\" && goRuntime.GOARCH == \"amd64\") {\n\t\ts.T().Skip(\"CI can't run linux docker containers inside non-linux runners\")\n\t}\n\trequire := s.Require()\n\tctx := s.Context()\n\n\tsvc := s.ServiceName()\n\ttag := \"telepresence/echo-test\"\n\ttestDir := \"testdata/echo-server\"\n\n\t_, err := itest.Output(ctx, \"docker\", \"build\", \"-t\", tag, testDir)\n\trequire.NoError(err)\n\n\tabs, err := filepath.Abs(testDir)\n\trequire.NoError(err)\n\n\tassertInterceptResponse := func(ctx context.Context) {\n\t\tassert := s.Assert()\n\t\tassert.EventuallyContext(ctx, func() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\treturn err == nil && strings.Contains(stdout, svc+\": intercepted\")\n\t\t}, 30*time.Second, 3*time.Second)\n\n\t\t// Response contains env variables TELEPRESENCE_CONTAINER and TELEPRESENCE_INTERCEPT_ID\n\t\texpectedOutput := regexp.MustCompile(`Intercept id [0-9a-f-]+:` + svc)\n\t\tassert.EventuallyContext(ctx, func() bool {\n\t\t\tout, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"1\", \"http://\"+svc)\n\t\t\tclog.Info(ctx, out)\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn expectedOutput.MatchString(out)\n\t\t},\n\t\t\t30*time.Second, // waitFor\n\t\t\t2*time.Second,  // polling interval\n\t\t\t`body of %q matches %q`, \"http://\"+svc, expectedOutput,\n\t\t)\n\t}\n\n\tassertNotIntercepted := func(ctx context.Context) {\n\t\tassert := s.Assert()\n\t\tassert.EventuallyContext(ctx, func() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif strings.Contains(stdout, svc+\": intercepted\") {\n\t\t\t\tclog.Debugf(ctx, \"stdout: %q\", stdout)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t}, 30*time.Second, 2*time.Second)\n\t}\n\n\tport := \"9070:8080\"\n\ts.Run(\"<ctrl>-C\", func() {\n\t\t// Use a soft context to send a <ctrl>-c to telepresence in order to end it\n\t\tctx := s.Context()\n\t\twch := make(chan struct{})\n\t\tproc := runDockerRun(ctx, \"\", svc, port, abs, tag, s.Require(), wch)\n\t\tassertInterceptResponse(ctx)\n\t\t_ = proc.Signal(os.Interrupt)\n\t\tselect {\n\t\tcase <-wch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.Fail(\"interceptor did not terminate\")\n\t\t}\n\t\tassertNotIntercepted(ctx)\n\t})\n\n\ts.Run(\"leave\", func() {\n\t\t// End the intercept from another telepresence invocation\n\t\tctx := s.Context()\n\t\twch := make(chan struct{})\n\t\trunDockerRun(ctx, \"\", svc, port, abs, tag, s.Require(), wch)\n\t\tassertInterceptResponse(ctx)\n\t\titest.TelepresenceOk(ctx, \"leave\", svc)\n\t\tselect {\n\t\tcase <-wch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.Fail(\"interceptor did not terminate\")\n\t\t}\n\t\tassertNotIntercepted(ctx)\n\t})\n\n\ts.Run(\"disconnect\", func() {\n\t\t// End the intercept from another telepresence invocation\n\t\tctx := s.Context()\n\t\twch := make(chan struct{})\n\t\trunDockerRun(ctx, \"\", svc, port, abs, tag, s.Require(), wch)\n\t\tassertInterceptResponse(ctx)\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\tselect {\n\t\tcase <-wch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.Fail(\"interceptor did not terminate\")\n\t\t}\n\t\ts.TelepresenceConnect(ctx)\n\t\tassertNotIntercepted(ctx)\n\t})\n\n\ts.Run(\"quit\", func() {\n\t\t// End the intercept from another telepresence invocation\n\t\tctx := s.Context()\n\t\twch := make(chan struct{})\n\t\trunDockerRun(ctx, \"\", svc, port, abs, tag, s.Require(), wch)\n\t\tassertInterceptResponse(ctx)\n\t\titest.TelepresenceQuitOk(ctx)\n\t\tselect {\n\t\tcase <-wch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.Fail(\"interceptor did not terminate\")\n\t\t}\n\t\ts.TelepresenceConnect(ctx)\n\t\tassertNotIntercepted(ctx)\n\t})\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerRun_DockerDaemon() {\n\tsvc := \"echo\"\n\tctx := s.Context()\n\ts.ApplyEchoService(ctx, svc, 80)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\trequire := s.Require()\n\tstdout := s.TelepresenceConnect(ctx, \"--docker\")\n\tdefer itest.TelepresenceQuitOk(ctx)\n\n\tmatch := regexp.MustCompile(`Connected to context ?(.+),\\s*namespace (\\S+)\\s+\\(`).FindStringSubmatch(stdout)\n\trequire.Len(match, 3)\n\n\ttag := \"telepresence/echo-test\"\n\ttestDir := \"testdata/echo-server\"\n\n\t_, err := itest.Output(ctx, \"docker\", \"build\", \"-t\", tag, testDir)\n\trequire.NoError(err)\n\n\tabs, err := filepath.Abs(testDir)\n\trequire.NoError(err)\n\n\tassertInterceptResponse := func(ctx context.Context) {\n\t\ts.Eventually(func() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\tclog.Info(ctx, stdout)\n\t\t\treturn err == nil && strings.Contains(stdout, svc+\": intercepted\")\n\t\t}, 30*time.Second, 3*time.Second)\n\n\t\texpectedOutput := regexp.MustCompile(`Intercept id [0-9a-f-]+:` + svc)\n\t\ts.Eventually(\n\t\t\t// condition\n\t\t\tfunc() bool {\n\t\t\t\tso, _, err := itest.Telepresence(ctx, \"curl\", \"--silent\", \"--max-time\", \"2\", \"http://\"+svc)\n\t\t\t\tclog.Info(ctx, so)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn expectedOutput.MatchString(so)\n\t\t\t},\n\t\t\t60*time.Second, // A docker container reuses IPs, but MAC-address changes. It takes time for the network to learn about this.\n\t\t\t5*time.Second,  // polling interval\n\t\t\t`body of %q matches %q`, \"http://\"+svc, expectedOutput,\n\t\t)\n\t}\n\n\tassertNotIntercepted := func(ctx context.Context) {\n\t\ts.Eventually(func() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\treturn err == nil && !strings.Contains(stdout, svc+\": intercepted\")\n\t\t}, 15*time.Second, 2*time.Second)\n\t}\n\n\ts.Run(\"<ctrl>-C\", func() {\n\t\t// Use a soft context to send a <ctrl>-c to telepresence in order to end it\n\t\tctx := s.Context()\n\t\twch := make(chan struct{})\n\t\tproc := runDockerRun(ctx, \"adam\", svc, \"\", abs, tag, s.Require(), wch)\n\t\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\t\tassertInterceptResponse(ctx)\n\t\t_ = proc.Signal(os.Interrupt)\n\t\tselect {\n\t\tcase <-wch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.Fail(\"interceptor did not terminate\")\n\t\t}\n\t\tassertNotIntercepted(ctx)\n\t})\n\n\ts.Run(\"leave\", func() {\n\t\t// End the intercept from another telepresence invocation\n\t\tctx := s.Context()\n\t\twch := make(chan struct{})\n\t\trunDockerRun(ctx, \"bruce\", svc, \"\", abs, tag, s.Require(), wch)\n\t\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\t\tassertInterceptResponse(ctx)\n\t\titest.TelepresenceOk(ctx, \"leave\", svc)\n\t\tselect {\n\t\tcase <-wch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.Fail(\"interceptor did not terminate\")\n\t\t}\n\t\tassertNotIntercepted(ctx)\n\t})\n\n\ts.Run(\"disconnect\", func() {\n\t\t// End the intercept from another telepresence invocation\n\t\tctx := s.Context()\n\t\twch := make(chan struct{})\n\t\trunDockerRun(ctx, \"chris\", svc, \"\", abs, tag, s.Require(), wch)\n\t\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\t\tassertInterceptResponse(ctx)\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\tselect {\n\t\tcase <-wch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.Fail(\"interceptor did not terminate\")\n\t\t}\n\t\ts.TelepresenceConnect(ctx, \"--docker\")\n\t\tassertNotIntercepted(ctx)\n\t})\n\n\ts.Run(\"quit\", func() {\n\t\t// End the intercept from another telepresence invocation\n\t\tctx := s.Context()\n\t\twch := make(chan struct{})\n\t\trunDockerRun(ctx, \"david\", svc, \"\", abs, tag, s.Require(), wch)\n\t\tassertInterceptResponse(ctx)\n\t\titest.TelepresenceQuitOk(ctx)\n\t\tselect {\n\t\tcase <-wch:\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.Fail(\"interceptor did not terminate\")\n\t\t}\n\t\ts.TelepresenceConnect(ctx, \"--docker\")\n\t\tassertNotIntercepted(ctx)\n\t})\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerRun_VolumePresent() {\n\tif !s.ClientIsVersion(\">2.24.x\") {\n\t\ts.T().Skip(\"Not part of compatibility tests. Docker volume plugin is unstable for versions < 2.25.0\")\n\t}\n\tctx := s.Context()\n\ts.ApplyTemplate(ctx, filepath.Join(\"testdata\", \"k8s\", \"hello-w-volumes.goyaml\"), nil)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", \"hello\")\n\n\ts.TelepresenceConnect(ctx, \"--docker\")\n\tdefer itest.TelepresenceQuitOk(ctx)\n\n\tstdout, _, err := itest.Telepresence(ctx, \"intercept\", \"--docker-run\", \"hello\", \"-p\", \"8080:http\", \"--\",\n\t\t\"--rm\", \"busybox\", \"ls\", \"/var/run/secrets/datawire.io/auth\")\n\ts.NoError(err)\n\tclog.Infof(ctx, \"stdout = %s\", stdout)\n\ts.True(strings.HasSuffix(stdout, \"\\nusername\"))\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerRunCommand() {\n\tctx := s.Context()\n\trequire := s.Require()\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--hostname\", \"cicero\")\n\tdefer itest.TelepresenceQuitOk(ctx)\n\n\tstdout, _, err := itest.Telepresence(ctx, \"docker-run\", \"--rm\", \"busybox\", \"ip\", \"r\")\n\trequire.NoError(err)\n\tclog.Infof(ctx, \"stdout = %s\", stdout)\n\tif s.ClientIsVersion(\">=2.23.0\") {\n\t\ts.Contains(stdout, \"dev tpd-0\")\n\t}\n}\n\nfunc (s *dockerDaemonSuite) Test_DockerRunExternalDNS() {\n\tctx := s.Context()\n\trequire := s.Require()\n\ts.TelepresenceConnect(ctx, \"--docker\")\n\tdefer itest.TelepresenceQuitOk(ctx)\n\n\tstdout, _, err := itest.Telepresence(ctx, \"docker-run\", \"--rm\", \"busybox\", \"nslookup\", \"google.com\")\n\trequire.NoError(err)\n\tclog.Infof(ctx, \"stdout = %s\", stdout)\n\ts.Contains(stdout, \"Address: \")\n}\n"
  },
  {
    "path": "integration_test/env_interpolate_test.go",
    "content": "package integration_test\n\nimport (\n\t\"github.com/go-json-experiment/json\"\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\nfunc (s *connectedSuite) Test_PrefixInterpolated() {\n\tctx := s.Context()\n\tsvc := \"echo-interpolate\"\n\trq := s.Require()\n\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\tdefer func() {\n\t\ts.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\t\ts.NoError(s.Kubectl(ctx, \"delete\", \"configmap\", \"interpolate-config\"))\n\t}()\n\n\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc)\n\tdefer itest.TelepresenceOk(ctx, \"leave\", svc)\n\tout, err := s.KubectlOut(ctx, \"get\", \"pod\", \"-o\", \"json\", \"-l\", \"app=\"+svc)\n\trq.NoError(err)\n\n\tvar pods core.PodList\n\terr = json.Unmarshal([]byte(out), &pods)\n\trq.NoError(err)\n\tvar ag *core.Container\nouter:\n\tfor _, pod := range pods.Items {\n\t\tcns := pod.Spec.Containers\n\t\tfor ci := range cns {\n\t\t\tcn := &cns[ci]\n\t\t\tif cn.Name == \"traffic-agent\" {\n\t\t\t\tag = cn\n\t\t\t\tbreak outer\n\t\t\t}\n\t\t}\n\t}\n\trq.NotNil(ag)\n\tfor _, vm := range ag.VolumeMounts {\n\t\tif vm.Name == \"my-volume\" {\n\t\t\trq.Equal(\"$(_TEL_APP_A_SOME_NAME)_$(_TEL_APP_A_OTHER_NAME)\", vm.SubPathExpr)\n\t\t\tbreak\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "integration_test/gather_logs_test.go",
    "content": "package integration_test\n\nimport (\n\t\"archive/zip\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/labels\"\n)\n\nfunc (s *multipleInterceptsSuite) TestGatherLogs_AllLogs() {\n\trequire := s.Require()\n\toutputDir := s.T().TempDir()\n\tctx := s.Context()\n\toutputFile := filepath.Join(outputDir, \"allLogs.zip\")\n\ts.cleanLogDir(ctx)\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--get-pod-yaml\", \"--output-file\", outputFile)\n\tfoundManager, foundAgents, yamlCount, fileNames := s.getZipData(outputFile)\n\trequire.True(foundManager)\n\trequire.Equal(s.ServiceCount(), foundAgents, fileNames)\n\t// One for each agent + one for the traffic manager\n\trequire.Equal(s.ServiceCount()+1, yamlCount, fileNames)\n}\n\nfunc (s *multipleInterceptsSuite) TestGatherLogs_ManagerOnly() {\n\trequire := s.Require()\n\toutputDir := s.T().TempDir()\n\tctx := s.Context()\n\toutputFile := filepath.Join(outputDir, \"allLogs.zip\")\n\ts.cleanLogDir(ctx)\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--output-file\", outputFile, \"--get-pod-yaml\", \"--traffic-agents=None\")\n\tfoundManager, foundAgents, yamlCount, fileNames := s.getZipData(outputFile)\n\trequire.True(foundManager)\n\trequire.Equal(0, foundAgents, fileNames)\n\trequire.GreaterOrEqual(yamlCount, 1, fileNames)\n}\n\nfunc (s *multipleInterceptsSuite) TestGatherLogs_AgentsOnly() {\n\trequire := s.Require()\n\toutputDir := s.T().TempDir()\n\tctx := s.Context()\n\toutputFile := filepath.Join(outputDir, \"allLogs.zip\")\n\ts.cleanLogDir(ctx)\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--output-file\", outputFile, \"--get-pod-yaml\", \"--traffic-manager=False\")\n\tfoundManager, foundAgents, yamlCount, fileNames := s.getZipData(outputFile)\n\trequire.False(foundManager)\n\trequire.GreaterOrEqual(foundAgents, s.ServiceCount(), fileNames)\n\trequire.GreaterOrEqual(yamlCount, s.ServiceCount(), fileNames)\n}\n\nfunc (s *multipleInterceptsSuite) TestGatherLogs_OneAgentOnly() {\n\trequire := s.Require()\n\toutputDir := s.T().TempDir()\n\tctx := s.Context()\n\toutputFile := filepath.Join(outputDir, \"allLogs.zip\")\n\ts.cleanLogDir(ctx)\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--output-file\", outputFile, \"--get-pod-yaml\", \"--traffic-manager=False\", \"--traffic-agents=hello-1\")\n\tfoundManager, foundAgents, yamlCount, fileNames := s.getZipData(outputFile)\n\trequire.False(foundManager)\n\trequire.GreaterOrEqual(foundAgents, 1, fileNames)\n\trequire.GreaterOrEqual(yamlCount, 1, fileNames)\n}\n\nfunc (s *multipleInterceptsSuite) TestGatherLogs_NoPodYamlUnlessLogs() {\n\trequire := s.Require()\n\toutputDir := s.T().TempDir()\n\tctx := s.Context()\n\toutputFile := filepath.Join(outputDir, \"allLogs.zip\")\n\ts.cleanLogDir(ctx)\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--output-file\", outputFile, \"--get-pod-yaml\", \"--traffic-manager=False\", \"--traffic-agents=None\")\n\tfoundManager, foundAgents, yamlCount, fileNames := s.getZipData(outputFile)\n\trequire.False(foundManager)\n\trequire.Equal(0, foundAgents, fileNames)\n\trequire.Equal(0, yamlCount, fileNames)\n}\n\nfunc (s *multipleInterceptsSuite) TestGatherLogs_NoK8sLogs() {\n\trequire := s.Require()\n\toutputDir := s.T().TempDir()\n\tctx := s.Context()\n\toutputFile := filepath.Join(outputDir, \"allLogs.zip\")\n\ts.cleanLogDir(ctx)\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--output-file\", outputFile, \"--get-pod-yaml\", \"--traffic-manager=False\", \"--traffic-agents=None\")\n\tfoundManager, foundAgents, yamlCount, fileNames := s.getZipData(outputFile)\n\trequire.False(foundManager)\n\trequire.Equal(0, foundAgents, fileNames)\n\trequire.Equal(0, yamlCount, fileNames)\n}\n\nfunc (s *connectedSuite) TestGatherLogs_OnlyMappedLogs() {\n\tconst svc = \"echo\"\n\tctx := s.Context()\n\titest.TelepresenceDisconnectOk(ctx)\n\n\totherOne := fmt.Sprintf(\"other-one-%s\", s.Suffix())\n\titest.CreateNamespaces(ctx, otherOne)\n\tdefer itest.DeleteNamespaces(ctx, otherOne)\n\n\totherTwo := fmt.Sprintf(\"other-two-%s\", s.Suffix())\n\titest.CreateNamespaces(ctx, otherTwo)\n\tdefer itest.DeleteNamespaces(ctx, otherTwo)\n\n\ts.TelepresenceHelmInstallOK(itest.WithNamespaces(ctx, &itest.Namespaces{\n\t\tNamespace: s.ManagerNamespace(),\n\t\tSelector:  labels.SelectorFromNames(otherOne, otherTwo),\n\t}), true)\n\n\trequire := s.Require()\n\tdefer func() {\n\t\tso, se, err := itest.Telepresence(ctx, \"quit\")\n\t\tclog.Debug(ctx, \"stdout\", so, \"stderr\", se, \"err\", err)\n\t\ts.RollbackTM(ctx)\n\t\tstdout := s.TelepresenceConnect(ctx)\n\t\trequire.Contains(stdout, \"Connected to context\")\n\t}()\n\n\titest.TelepresenceDisconnectOk(ctx)\n\titest.ApplyEchoService(ctx, svc, otherOne, 8083)\n\titest.ApplyEchoService(ctx, svc, otherTwo, 8084)\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--namespace\", otherOne, \"--manager-namespace\", s.ManagerNamespace())\n\n\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc)\n\ts.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\treturn err == nil && strings.Contains(stdout, svc+\": intercepted\")\n\t\t},\n\t\t10*time.Second,\n\t\t2*time.Second,\n\t)\n\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", otherOne)\n\titest.TelepresenceDisconnectOk(ctx)\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--namespace\", otherTwo, \"--manager-namespace\", s.ManagerNamespace())\n\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc)\n\ts.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\treturn err == nil && strings.Contains(stdout, svc+\": intercepted\")\n\t\t},\n\t\t10*time.Second,\n\t\t2*time.Second,\n\t)\n\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", otherTwo)\n\titest.TelepresenceOk(ctx, \"leave\", svc)\n\n\tbothNsRx := fmt.Sprintf(\"(?:%s|%s)\", otherOne, otherTwo)\n\toutputDir := s.T().TempDir()\n\toutputFile := filepath.Join(outputDir, \"allLogs.zip\")\n\titest.CleanLogDir(ctx, require, bothNsRx, s.ManagerNamespace(), svc)\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--output-file\", outputFile, \"--traffic-manager=False\")\n\t_, foundAgents, _, fileNames := getZipData(require, outputFile, bothNsRx, s.ManagerNamespace(), svc)\n\trequire.GreaterOrEqual(foundAgents, 2, fileNames)\n\n\t// Connect using mapped-namespaces\n\titest.TelepresenceDisconnectOk(ctx)\n\tstdout := itest.TelepresenceOk(ctx, \"connect\", \"--namespace\", otherOne, \"--manager-namespace\", s.ManagerNamespace(), \"--mapped-namespaces\", otherOne)\n\trequire.Contains(stdout, \"Connected to context\")\n\n\titest.CleanLogDir(ctx, require, bothNsRx, s.ManagerNamespace(), svc)\n\titest.TelepresenceOk(ctx, \"list\") // To ensure that the mapped namespaces are active\n\titest.TelepresenceOk(ctx, \"gather-logs\", \"--output-file\", outputFile, \"--traffic-manager=False\")\n\t_, foundAgents, _, fileNames = getZipData(require, outputFile, bothNsRx, s.ManagerNamespace(), svc)\n\trequire.GreaterOrEqual(foundAgents, 1, fileNames)\n}\n\nfunc (s *multipleInterceptsSuite) cleanLogDir(ctx context.Context) {\n\titest.CleanLogDir(ctx, s.Require(), s.AppNamespace(), s.ManagerNamespace(), s.svcRegex())\n}\n\nfunc (s *multipleInterceptsSuite) svcRegex() string {\n\tif s.ServiceCount() >= 10 {\n\t\treturn `hello-\\d+`\n\t}\n\treturn fmt.Sprintf(\"hello-[0-%d]\", s.ServiceCount())\n}\n\nfunc (s *multipleInterceptsSuite) getZipData(outputFile string) (bool, int, int, []string) {\n\treturn getZipData(s.Require(), outputFile, s.AppNamespace(), s.ManagerNamespace(), s.svcRegex())\n}\n\nfunc getZipData(require *itest.Requirements, outputFile, appNamespace, mgrNamespace, svcNameRx string) (bool, int, int, []string) {\n\tzipReader, err := zip.OpenReader(outputFile)\n\trequire.NoError(err)\n\tdefer func() {\n\t\trequire.NoError(zipReader.Close())\n\t}()\n\t// we collect and return the fileNames so that it makes it easier\n\t// to debug if tests fail\n\thelloMatch := regexp.MustCompile(fmt.Sprintf(`^%s-[0-9a-z-]+\\.%s\\.(?:log|yaml)$`, svcNameRx, appNamespace))\n\ttmMatch := regexp.MustCompile(fmt.Sprintf(`^traffic-manager-[0-9a-z-]+\\.%s\\.(?:log|yaml)$`, mgrNamespace))\n\ttmHdrMatch := regexp.MustCompile(`Traffic Manager v\\d+\\.\\d+\\.\\d+`)\n\tagHdrMatch := regexp.MustCompile(`Traffic Agent v\\d+\\.\\d+\\.\\d+`)\n\tfoundManager, foundAgents, yamlCount := false, 0, 0\n\tfileNames := make([]string, len(zipReader.File))\n\tfor i, f := range zipReader.File {\n\t\tfileNames[i] = f.Name\n\t\tif tmMatch.MatchString(f.Name) {\n\t\t\tif strings.HasSuffix(f.Name, \".yaml\") {\n\t\t\t\tyamlCount++\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfileContent := readZip(require, f)\n\t\t\t// We can be fairly certain we actually got a traffic-manager log\n\t\t\t// if we see the following\n\t\t\tif tmHdrMatch.Match(fileContent) {\n\t\t\t\tfoundManager = true\n\t\t\t}\n\t\t}\n\t\tif helloMatch.MatchString(f.Name) {\n\t\t\tif strings.HasSuffix(f.Name, \".yaml\") {\n\t\t\t\tyamlCount++\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfileContent := readZip(require, f)\n\t\t\t// We can be fairly certain we actually got a traffic-agent log\n\t\t\t// if we see the following\n\t\t\tif agHdrMatch.Match(fileContent) {\n\t\t\t\tfoundAgents++\n\t\t\t}\n\t\t}\n\t}\n\treturn foundManager, foundAgents, yamlCount, fileNames\n}\n\n// readZip reads a zip file and returns the []byte string. Used in tests for\n// checking that a zipped file's contents are correct. Exported since it is\n// also used in telepresence_test.go.\nfunc readZip(require *itest.Requirements, zippedFile *zip.File) []byte {\n\tfileReader, err := zippedFile.Open()\n\trequire.NoError(err)\n\tfileContent, err := io.ReadAll(fileReader)\n\trequire.NoError(err)\n\treturn fileContent\n}\n"
  },
  {
    "path": "integration_test/h2c_intercept_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n)\n\ntype h2cInterceptSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *h2cInterceptSuite) SuiteName() string {\n\treturn \"H2CIntercept\"\n}\n\nfunc init() {\n\titest.AddConnectedSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &h2cInterceptSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *h2cInterceptSuite) SetupSuite() {\n\tif !(s.ManagerIsVersion(\">2.26.x\") && s.ClientIsVersion(\">2.26.x\")) {\n\t\ts.T().Skip(\"H2C intercepts require Telepresence 2.27.0 or later\")\n\t}\n\ts.Suite.SetupSuite()\n}\n\n// startLocalH2CEchoServer starts a local HTTP server that supports h2c (HTTP/2 cleartext)\n// and echoes a line with the given name and the current URL path.\nfunc startLocalH2CEchoServer(ctx context.Context, name string) (int, context.CancelFunc) {\n\tctx, cancel := context.WithCancel(ctx)\n\tlc := net.ListenConfig{}\n\tl, err := lc.Listen(ctx, \"tcp\", \"localhost:0\")\n\tif err != nil {\n\t\tcancel()\n\t\tpanic(fmt.Sprintf(\"failed to listen: %v\", err))\n\t}\n\tpr := new(http.Protocols)\n\tpr.SetHTTP1(true)\n\tpr.SetUnencryptedHTTP2(true)\n\tsc := &http.Server{\n\t\tHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tfmt.Fprintf(w, \"%s from intercept at %s\", name, r.URL.Path)\n\t\t}),\n\t\tProtocols: pr,\n\t}\n\tgo func() {\n\t\t_ = sc.Serve(l)\n\t}()\n\tgo func() {\n\t\t<-ctx.Done()\n\t\tsdCtx, sdCancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second)\n\t\tdefer sdCancel()\n\t\t_ = sc.Shutdown(sdCtx)\n\t}()\n\treturn l.Addr().(*net.TCPAddr).Port, cancel\n}\n\nfunc (s *h2cInterceptSuite) Test_H2CInterceptPreservesProtocol() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\tconst svc = \"echo-h2c\"\n\n\t// Deploy echo-server with appProtocol: kubernetes.io/h2c\n\titest.ApplyAppTemplate(ctx, s.AppNamespace(), &itest.AppData{\n\t\tAppName: svc,\n\t\tPorts: []itest.AppPort{\n\t\t\t{\n\t\t\t\tServicePortNumber: 80,\n\t\t\t\tTargetPortNumber:  8080,\n\t\t\t\tAppProtocol:       \"kubernetes.io/h2c\",\n\t\t\t},\n\t\t},\n\t\tEnv: map[string]string{\"PORTS\": \"8080:http\"},\n\t})\n\tdefer itest.DeleteSvcAndWorkload(ctx, \"deploy\", svc, s.AppNamespace())\n\n\t// Start a local h2c-capable echo server to receive intercepted traffic.\n\t// The upstream handler must support the same protocol as the app.\n\tlocalPort, cancel := startLocalH2CEchoServer(ctx, svc)\n\tdefer cancel()\n\n\t// Create a personal HTTP intercept with a header filter\n\tstdout, stderr, err := itest.Telepresence(ctx, \"intercept\", svc,\n\t\t\"--http-header\", \"x-test=h2c\",\n\t\t\"--port\", strconv.Itoa(localPort)+\":80\",\n\t\t\"--mount=false\")\n\trequire.NoError(err, \"stderr: %s\", stderr)\n\trequire.Contains(stdout, \"Using Deployment\")\n\n\t// Capture traffic-agent logs for debugging\n\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\n\t// Verify non-intercepted traffic uses HTTP/2 (h2c).\n\t// The intercept is active so traffic goes through the agent's HTTP handler.\n\t// Requests without the x-test header don't match the intercept and are forwarded\n\t// by the default reverse proxy. The fix ensures that this proxy uses h2c prior\n\t// knowledge, so the echo-server sees HTTP/2.0.\n\trequire.Eventually(func() bool {\n\t\tips, err := net.DefaultResolver.LookupIP(ctx, \"ip\", svc)\n\t\tif err != nil {\n\t\t\tclog.Info(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tips = iputil.UniqueSorted(ips)\n\t\tif len(ips) != 1 {\n\t\t\tclog.Infof(ctx, \"Lookup for %s returned %v\", svc, ips)\n\t\t\treturn false\n\t\t}\n\t\thc := http.Client{Timeout: 2 * time.Second}\n\t\trq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"http://%s\", net.JoinHostPort(ips[0].String(), \"80\")), nil)\n\t\tif err != nil {\n\t\t\tclog.Info(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tresp, err := hc.Do(rq)\n\t\tif err != nil {\n\t\t\tclog.Info(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tdefer resp.Body.Close()\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tclog.Info(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tr := string(body)\n\t\tclog.Infof(ctx, \"non-intercepted response: %s\", r)\n\t\treturn strings.Contains(r, \"HTTP/2.0 GET /\")\n\t}, 30*time.Second, 3*time.Second, \"expected HTTP/2.0 in non-intercepted response\")\n\n\t// Verify intercepted traffic reaches the local h2c handler\n\titest.PingInterceptedEchoServer(ctx, svc, \"80\", \"x-test=h2c\")\n\n\t// Leave the intercept\n\t_, _, err = itest.Telepresence(ctx, \"leave\", svc)\n\trequire.NoError(err)\n}\n"
  },
  {
    "path": "integration_test/headless_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\nfunc (s *connectedSuite) Test_SuccessfullyInterceptsHeadlessService() {\n\tif itest.GetProfile(s.Context()) == itest.GkeAutopilotProfile {\n\t\ts.T().Skip(\"GKE Autopilot does not support NET_ADMIN containers which means headless services can't be intercepted\")\n\t}\n\tctx, cancel := context.WithCancel(s.Context())\n\tdefer cancel()\n\tconst svc = \"echo-headless\"\n\n\tsvcPort, svcCancel := itest.StartLocalHttpEchoServer(ctx, svc)\n\tdefer svcCancel()\n\n\ts.ApplyApp(ctx, \"echo-headless\", \"statefulset/echo-headless\")\n\tdefer s.DeleteSvcAndWorkload(ctx, \"statefulset\", \"echo-headless\")\n\trequire := s.Require()\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc, \"--port\", strconv.Itoa(svcPort))\n\trequire.Contains(stdout, \"Using StatefulSet echo-headless\")\n\ts.CapturePodLogs(ctx, \"echo-headless\", \"traffic-agent\", s.AppNamespace())\n\n\tdefer itest.TelepresenceOk(ctx, \"leave\", \"echo-headless\")\n\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\treturn err == nil && strings.Contains(stdout, \"echo-headless: intercepted\")\n\t\t},\n\t\t30*time.Second, // waitFor\n\t\t3*time.Second,  // polling interval\n\t\t`intercepted workload never show up in list`)\n\n\titest.PingInterceptedEchoServer(ctx, svc, \"8080\")\n}\n"
  },
  {
    "path": "integration_test/helm_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/labels\"\n)\n\ntype helmSuite struct {\n\titest.Suite\n\titest.SingleService\n\tmgrSpace2 string\n\tappSpace2 string\n}\n\nfunc (s *helmSuite) SuiteName() string {\n\treturn \"Helm\"\n}\n\nfunc init() {\n\titest.AddSingleServiceSuite(\"\", \"echo\", func(h itest.SingleService) itest.TestingSuite {\n\t\ts := &helmSuite{Suite: itest.Suite{Harness: h}, SingleService: h}\n\t\tsuffix := itest.GetGlobalHarness(h.HarnessContext()).Suffix()\n\t\ts.appSpace2, s.mgrSpace2 = itest.AppAndMgrNSName(suffix + \"-2\")\n\t\treturn s\n\t})\n}\n\nfunc (s *helmSuite) SetupSuite() {\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\titest.CreateNamespaces(ctx, s.appSpace2, s.mgrSpace2)\n\titest.ApplyEchoService(ctx, s.ServiceName(), s.appSpace2, 80)\n}\n\nfunc (s *helmSuite) TearDownSuite() {\n\titest.DeleteNamespaces(s.Context(), s.appSpace2, s.mgrSpace2)\n}\n\nfunc (s *helmSuite) Test_HelmCanInterceptInManagedNamespace() {\n\tctx := s.Context()\n\tdefer itest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", s.ServiceName(), \"--port\", \"9090\")\n\ts.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\ts.Contains(stdout, s.ServiceName()+\": intercepted\")\n}\n\nfunc (s *helmSuite) Test_HelmCannotConnectToUnmanagedNamespace() {\n\tctx := s.Context()\n\tdefer func() {\n\t\tctx := s.Context()\n\t\t_, _, _ = itest.Telepresence(ctx, \"disconnect\") //nolint:dogsled // X\n\t\ts.TelepresenceConnect(ctx)\n\t}()\n\titest.TelepresenceDisconnectOk(ctx)\n\t_, stderr, err := itest.Telepresence(ctx, \"connect\", \"--namespace\", s.appSpace2, \"--manager-namespace\", s.ManagerNamespace())\n\ts.Error(err)\n\ts.True(strings.Contains(stderr, fmt.Sprintf(`namespace %s is not managed`, s.appSpace2)))\n}\n\nfunc (s *helmSuite) Test_HelmWebhookInjectsInManagedNamespace() {\n\tctx := s.Context()\n\ts.ApplyApp(ctx, \"echo-auto-inject\", \"deploy/echo-auto-inject\")\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", \"echo-auto-inject\")\n\n\tverb := \"engage\"\n\tif !s.ClientIsVersion(\">2.21.x\") {\n\t\tverb = \"intercept\"\n\t}\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--agents\")\n\t\treturn err == nil && strings.Contains(stdout, fmt.Sprintf(\"echo-auto-inject: ready to %s (traffic-agent already installed)\", verb))\n\t},\n\t\t20*time.Second, // waitFor\n\t\t2*time.Second,  // polling interval\n\t)\n}\n\nfunc (s *helmSuite) Test_HelmWebhookDoesntInjectInUnmanagedNamespace() {\n\tctx := s.Context()\n\titest.ApplyApp(ctx, \"echo-auto-inject\", s.appSpace2, \"deploy/echo-auto-inject\")\n\tdefer itest.DeleteSvcAndWorkload(ctx, \"deploy\", \"echo-auto-inject\", s.appSpace2)\n\n\tverb := \"engage\"\n\tif !s.ClientIsVersion(\">2.21.x\") {\n\t\tverb = \"intercept\"\n\t}\n\ts.Never(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--namespace\", s.appSpace2, \"--agents\")\n\t\treturn err == nil && strings.Contains(stdout, fmt.Sprintf(\"echo-auto-inject: ready to %s (traffic-agent already installed)\", verb))\n\t},\n\t\t10*time.Second, // waitFor\n\t\t2*time.Second,  // polling interval\n\t)\n}\n\nfunc (s *helmSuite) Test_HelmMultipleInstalls() {\n\tsvc := s.ServiceName()\n\tdefer func() {\n\t\tctx := s.Context()\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\ts.TelepresenceConnect(ctx)\n\t}()\n\n\ts.Run(\"Installs Successfully\", func() {\n\t\tctx := itest.WithNamespaces(s.Context(), &itest.Namespaces{\n\t\t\tNamespace: s.mgrSpace2,\n\t\t\tSelector:  labels.SelectorFromNames(s.appSpace2),\n\t\t})\n\t\ts.NoError(itest.Kubectl(ctx, s.mgrSpace2, \"apply\", \"-f\", filepath.Join(\"testdata\", \"k8s\", \"client_sa.yaml\")))\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\ts.TelepresenceHelmInstallOK(ctx, false)\n\t})\n\n\ts.Run(\"Can be connected to\", func() {\n\t\tctx := itest.WithUser(s.Context(), s.mgrSpace2+\":\"+itest.TestUser)\n\t\tstdout := itest.TelepresenceOk(ctx, \"connect\", \"--namespace\", s.appSpace2, \"--manager-namespace\", s.mgrSpace2)\n\t\ts.Contains(stdout, \"Connected to context\")\n\t\ts.Eventually(func() bool {\n\t\t\treturn itest.Run(ctx, \"curl\", \"--silent\", \"--connect-timeout\", \"1\", fmt.Sprintf(\"%s.%s\", svc, s.appSpace2)) == nil\n\t\t}, 30*time.Second, 3*time.Second)\n\t})\n\n\ts.Run(\"Can intercept\", func() {\n\t\tctx := s.Context()\n\t\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc, \"--port\", \"9090\")\n\t\ts.Contains(stdout, \"Using Deployment \"+svc)\n\t\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--namespace\", s.appSpace2, \"--intercepts\")\n\t\ts.Contains(stdout, svc+\": intercepted\")\n\t})\n\n\ts.Run(\"Uninstalls Successfully\", func() {\n\t\tdefer itest.TelepresenceQuitOk(s.Context())\n\t\ts.UninstallTrafficManager(s.Context(), s.mgrSpace2)\n\t})\n}\n\nfunc (s *helmSuite) Test_CollidingInstalls() {\n\tdefer func() {\n\t\tctx := s.Context()\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\ts.TelepresenceConnect(ctx)\n\t}()\n\tctx := itest.WithNamespaces(s.Context(), &itest.Namespaces{\n\t\tNamespace: s.AppNamespace(),\n\t\tSelector:  labels.SelectorFromNames(s.appSpace2),\n\t})\n\t_, err := s.TelepresenceHelmInstall(ctx, false)\n\ts.Error(err)\n}\n"
  },
  {
    "path": "integration_test/http_intercepts_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype httpInterceptsSuite struct {\n\titest.Suite\n\titest.SingleService\n}\n\nfunc (s *httpInterceptsSuite) SuiteName() string {\n\treturn \"HTTPIntercepts\"\n}\n\nfunc (s *httpInterceptsSuite) SetupSuite() {\n\tif !(s.ManagerIsVersion(\">2.24.x\") && s.ClientIsVersion(\">2.24.x\")) {\n\t\ts.T().Skip(\"HTTP intercepts require Telepresence 2.25.0 or later\")\n\t}\n\ts.Suite.SetupSuite()\n}\n\nfunc (s *httpInterceptsSuite) Test_HTTPHeaderFiltering() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\t// Test HTTP intercept with header filters using equals format\n\tstdout, stderr, err := itest.Telepresence(ctx, \"intercept\", s.ServiceName(), \"--http-header\", \"X-User-ID=dev123\", \"--port\", \"8080\")\n\trequire.NoError(err, \"stderr: %s\", stderr)\n\trequire.Contains(stdout, \"Using Deployment\")\n\n\t// Clean up\n\t_, _, err = itest.Telepresence(ctx, \"leave\", s.ServiceName())\n\trequire.NoError(err)\n}\n\nfunc (s *httpInterceptsSuite) Test_HTTPHeaderFiltering_CurlFormat() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\t// Test HTTP intercept with header filters using colon format (curl -H compatible)\n\tstdout, stderr, err := itest.Telepresence(ctx, \"intercept\", s.ServiceName(), \"--http-header\", \"X-User-ID: dev123\", \"--port\", \"8080\")\n\trequire.NoError(err, \"stderr: %s\", stderr)\n\trequire.Contains(stdout, \"Using Deployment\")\n\n\t// Clean up\n\t_, _, err = itest.Telepresence(ctx, \"leave\", s.ServiceName())\n\trequire.NoError(err)\n}\n\nfunc (s *httpInterceptsSuite) Test_HTTPPathFiltering() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\t// Test HTTP intercept with path filters\n\tstdout, stderr, err := itest.Telepresence(ctx, \"intercept\", s.ServiceName(), \"--http-path-prefix\", \"/api/\", \"--port\", \"8080\")\n\trequire.NoError(err, \"stderr: %s\", stderr)\n\trequire.Contains(stdout, \"Using Deployment\")\n\n\t// Clean up\n\t_, _, err = itest.Telepresence(ctx, \"leave\", s.ServiceName())\n\trequire.NoError(err)\n}\n\nfunc (s *httpInterceptsSuite) Test_HTTPCombinedFiltering() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\t// Test HTTP intercept with both header and path filters, using mixed formats\n\tstdout, stderr, err := itest.Telepresence(ctx, \"intercept\", s.ServiceName(),\n\t\t\"--http-header\", \"X-User-ID=dev123\",\n\t\t\"--http-header\", \"Authorization: Bearer token123\",\n\t\t\"--http-path-prefix\", \"/api/\",\n\t\t\"--port\", \"8080\")\n\trequire.NoError(err, \"stderr: %s\", stderr)\n\trequire.Contains(stdout, \"Using Deployment\")\n\n\t// Clean up\n\t_, _, err = itest.Telepresence(ctx, \"leave\", s.ServiceName())\n\trequire.NoError(err)\n}\n\nfunc (s *httpInterceptsSuite) Test_BackwardCompatibility() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\t// Test that standard TCP intercepts still work without HTTP filters\n\tstdout, stderr, err := itest.Telepresence(ctx, \"intercept\", s.ServiceName(), \"--port\", \"8080\")\n\trequire.NoError(err, \"stderr: %s\", stderr)\n\trequire.Contains(stdout, \"Using Deployment\")\n\n\t// Clean up\n\t_, _, err = itest.Telepresence(ctx, \"leave\", s.ServiceName())\n\trequire.NoError(err)\n}\n\nfunc (s *httpInterceptsSuite) Test_HTTPInterceptCoexistence() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\t// This test verifies that multiple personal intercepts with different HTTP headers\n\t// can coexist on the same workload without conflicts (fixes issue #3969)\n\n\t// Start first personal intercept with x-user=adam\n\tstdout1, stderr1, err1 := itest.Telepresence(ctx, \"intercept\", \"echo-one\",\n\t\t\"--workload\", s.ServiceName(),\n\t\t\"--http-header\", \"x-user=adam\",\n\t\t\"--port\", \"8080:80\",\n\t\t\"--mount\", \"false\")\n\trequire.NoError(err1, \"First intercept failed - stderr: %s\", stderr1)\n\trequire.Contains(stdout1, \"Using Deployment\")\n\n\t// Start second personal intercept with x-user=bertil\n\tstdout2, stderr2, err2 := itest.Telepresence(ctx, \"intercept\", \"echo-two\",\n\t\t\"--workload\", s.ServiceName(),\n\t\t\"--http-header\", \"x-user=bertil\",\n\t\t\"--port\", \"8081:80\",\n\t\t\"--mount\", \"false\")\n\trequire.NoError(err2, \"Second intercept failed - stderr: %s\", stderr2)\n\trequire.Contains(stdout2, \"Using Deployment\")\n\n\t// Verify both intercepts are active and listed\n\tlistOutput, listStderr, listErr := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\trequire.NoError(listErr, \"Failed to list intercepts - stderr: %s\", listStderr)\n\trequire.Contains(listOutput, \"Intercept name: echo-one\")\n\trequire.Contains(listOutput, \"Intercept name: echo-two\")\n\n\t// Clean up both intercepts\n\t_, _, err3 := itest.Telepresence(ctx, \"leave\", \"echo-one\")\n\trequire.NoError(err3, \"Failed to leave first intercept\")\n\n\t_, _, err4 := itest.Telepresence(ctx, \"leave\", \"echo-two\")\n\trequire.NoError(err4, \"Failed to leave second intercept\")\n}\n\nfunc (s *httpInterceptsSuite) Test_TCPPortConflictDetection() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\t// This test verifies that real TCP port conflicts are still properly detected\n\t// when two intercepts try to use the same local port\n\n\t// Start first intercept using port 8080\n\tstdout1, stderr1, err1 := itest.Telepresence(ctx, \"intercept\", \"tcp-conflict-one\",\n\t\t\"--workload\", s.ServiceName(),\n\t\t\"--http-header\", \"x-user=adam\",\n\t\t\"--port\", \"8080:80\",\n\t\t\"--mount\", \"false\")\n\trequire.NoError(err1, \"First intercept should succeed - stderr: %s\", stderr1)\n\trequire.Contains(stdout1, \"Using Deployment\")\n\n\t// Attempt second intercept using the SAME local port 8080\n\t// This should fail with a TCP port conflict, not an agent intercept conflict\n\t_, stderr2, err2 := itest.Telepresence(ctx, \"intercept\", \"tcp-conflict-two\",\n\t\t\"--workload\", s.ServiceName(),\n\t\t\"--http-header\", \"x-user=bertil\",\n\t\t\"--port\", \"8080:80\", // Same local port as first intercept\n\t\t\"--mount\", \"false\")\n\n\t// Should fail due to real TCP port conflict\n\trequire.Error(err2, \"Second intercept should fail due to TCP port conflict\")\n\n\t// Verify it's a TCP port binding error, not an agent intercept conflict\n\trequire.Contains(stderr2, \"127.0.0.1:8080\", \"Error should mention the conflicting local port\")\n\trequire.Contains(stderr2, \"already in use\", \"Error should indicate port is already in use\")\n\n\t// Should NOT contain agent intercept conflict message\n\trequire.NotContains(stderr2, \"Conflicts with\", \"Should not be an agent intercept conflict\")\n\n\t// Clean up the successful intercept\n\t_, _, err3 := itest.Telepresence(ctx, \"leave\", \"tcp-conflict-one\")\n\trequire.NoError(err3, \"Failed to leave first intercept\")\n}\n\nfunc init() {\n\titest.AddSingleServiceSuite(\"\", \"echo\", func(h itest.SingleService) itest.TestingSuite {\n\t\treturn &httpInterceptsSuite{Suite: itest.Suite{Harness: h}, SingleService: h}\n\t})\n}\n\nfunc (s *httpInterceptsSuite) Test_HTTPManySimultaneous() {\n\tif _, ok := os.LookupEnv(\"HTTP_INTERCEPT_STRESS_TEST\"); !ok {\n\t\ts.T().Skip(\"Run this stress manually. It's too demanding for the CI infrastructure.\")\n\t\treturn\n\t}\n\trequire := s.Require()\n\tctx := s.Context()\n\n\tconst interceptCount = 250\n\tconst pingRepeatCount = 10\n\tlocalPorts := make([]int, interceptCount)\n\thttpCancels := make([]context.CancelFunc, interceptCount)\n\tresponseFunc := func(name string, r *http.Request) string {\n\t\treturn fmt.Sprintf(\"%s, X-Personal-Id: %s, X-Repeat-Count: %s\",\n\t\t\tname, r.Header.Get(\"X-Personal-Id\"), r.Header.Get(\"X-Repeat-Count\"))\n\t}\n\n\t// Ensure that a traffic-agent is running on the workload and capture its log\n\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", s.ServiceName())\n\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n\ts.CapturePodLogs(ctx, s.ServiceName(), \"traffic-agent\", s.AppNamespace())\n\n\tfor i := 0; i < interceptCount; i++ {\n\t\tid := strconv.Itoa(i)\n\t\tlocalPorts[i], httpCancels[i] = itest.StartLocalHttpEchoServerWithAddr(ctx, \"echo-\"+id, \"localhost:0\", responseFunc)\n\t}\n\tdefer func() {\n\t\tfor _, cancel := range httpCancels {\n\t\t\tcancel()\n\t\t}\n\t}()\n\n\tfor i := 0; i < interceptCount; i++ {\n\t\tid := strconv.Itoa(i)\n\t\thdr := \"X-Personal-Id=\" + id\n\t\tsvc := \"echo-\" + id\n\t\tstdout, stderr, err := itest.Telepresence(ctx, \"intercept\", svc,\n\t\t\t\"--workload\", s.ServiceName(),\n\t\t\t\"--http-header\", hdr,\n\t\t\t\"--port\", strconv.Itoa(localPorts[i])+\":80\",\n\t\t\t\"--mount\", \"false\")\n\t\trequire.NoError(err, \"stderr: %s\", stderr)\n\t\trequire.Contains(stdout, \"Using Deployment\")\n\t}\n\n\twg := &sync.WaitGroup{}\n\twg.Add(interceptCount * pingRepeatCount)\n\tfor i := 0; i < interceptCount; i++ {\n\t\tfor n := 0; n < pingRepeatCount; n++ {\n\t\t\tgo func(i int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\thdr := \"X-Personal-Id=\" + strconv.Itoa(i)\n\t\t\t\trpt := \"X-Repeat-Count=\" + strconv.Itoa(n)\n\t\t\t\texpectedOutput := fmt.Sprintf(\"echo-%d, X-Personal-Id: %d, X-Repeat-Count: %d\", i, i, n)\n\t\t\t\titest.PingInterceptedEchoServerAndExpect(ctx, s.ServiceName(), \"80\", expectedOutput, hdr, rpt)\n\t\t\t}(i)\n\t\t}\n\t}\n\twg.Wait()\n\n\tfor i := 0; i < interceptCount; i++ {\n\t\tid := strconv.Itoa(i)\n\t\t_, _, err := itest.Telepresence(ctx, \"leave\", \"echo-\"+id)\n\t\trequire.NoError(err, \"Failed to leave intercept echo-\"+id)\n\t}\n}\n\nfunc (s *notConnectedSuite) Test_HTTPManyClientsSimultaneous() {\n\ttestHTTPManyClientsSimultaneous(s, \"echo-easy\", \"/\")\n}\n\nfunc (s *otelSuite) Test_OtelHTTPManyClientsSimultaneous() {\n\ttestHTTPManyClientsSimultaneous(s, \"echo-spring\", \"/rest/echo\")\n}\n\ntype NamespaceSuite interface {\n\titest.NamespacePair\n\tT() *testing.T\n\tContext() context.Context\n\tContains(actual any, expected any, msgAndArgs ...any) bool\n\tNoError(err error, msgAndArgs ...any) bool\n\tFailNow(msg string, args ...any) bool\n\tEventually(f func() bool, timeout time.Duration, tick time.Duration, msgAndArgs ...any) bool\n}\n\nfunc testHTTPManyClientsSimultaneous(s NamespaceSuite, svc, path string) {\n\tif _, ok := os.LookupEnv(\"HTTP_INTERCEPT_STRESS_TEST\"); !ok {\n\t\ts.T().Skip(\"Run this stress manually. It's too demanding for the CI infrastructure.\")\n\t\treturn\n\t}\n\tctx := s.Context()\n\n\t// High values here will likely cause errors like \"too many open files\" unless the docker service is configured to allow more.\n\t// On a Linux box, this is typically done by adding a /etc/systemd/system/docker.service.d/override.conf file with the following contents:\n\t// [Service]\n\t// LimitNOFILE=infinity\n\t//\n\t// Also add the following line to /etc/docker/daemon.json:\n\t// {\n\t// \t\"default-ulimits\": {\n\t//\t\t\"nofile\": {\n\t//\t\t\t\"Name\": \"nofile\",\n\t//\t\t\t\"Soft\": 64000,\n\t//\t\t\t\"Hard\": 64000\n\t//\t\t}\n\t//\t}\n\t// }\n\tconst interceptCount = 16\n\n\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\ts.TelepresenceConnect(ctx)\n\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc)\n\titest.TelepresenceQuitOk(ctx)\n\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\n\tconns := make([]string, 0, interceptCount)\n\tdefer func() {\n\t\tfor _, connName := range conns {\n\t\t\t_, _, err := itest.Telepresence(ctx, \"--use\", connName, \"quit\")\n\t\t\ts.NoError(err)\n\t\t}\n\t}()\n\n\tfor i := 0; i < interceptCount; i++ {\n\t\tid := strconv.Itoa(i)\n\t\tconnName := \"conn-\" + id + \"-v\"\n\t\t_, err := s.TelepresenceTryConnect(ctx, \"--docker\", \"--name\", connName)\n\t\tif err != nil {\n\t\t\ts.FailNow(\"Failed to connect to telepresence\", err)\n\t\t}\n\t\tconns = append(conns, connName)\n\t}\n\n\twg := &sync.WaitGroup{}\n\twg.Add(interceptCount)\n\tfor i := 0; i < interceptCount; i++ {\n\t\tid := strconv.Itoa(i)\n\t\thdr := \"X-Personal-Id=\" + id\n\t\tconnName := conns[i]\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ttpCtx, cancel := context.WithCancel(ctx)\n\t\t\toutCh := make(chan string)\n\t\t\tgo func() {\n\t\t\t\tdefer close(outCh)\n\t\t\t\tstdout, stderr, err := itest.Telepresence(tpCtx, \"--use\", connName, \"intercept\", svc, \"--mount=false\", \"--http-header\", hdr, \"--docker-run\", \"--port\", \"8080:80\", \"--\", \"--name\", connName+\".local\", \"telepresenceio/echo-server\")\n\t\t\t\ts.NoError(err, \"stderr: %s\", stderr)\n\t\t\t\toutCh <- stdout\n\t\t\t}()\n\t\t\ts.Eventually(func() bool {\n\t\t\t\tso, se, err := itest.Telepresence(ctx, \"--use\", connName, \"curl\", \"--silent\", \"--max-time\", \"2\", \"-H\", \"X-Personal-Id: \"+id, svc+path)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Error(ctx, so, se, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\treturn strings.Contains(so, \"Intercepted container\")\n\t\t\t}, 10*time.Second, 1*time.Second)\n\n\t\t\titest.TelepresenceOk(ctx, \"--use\", connName, \"quit\")\n\t\t\tcancel()\n\t\t\t<-outCh\n\t\t}()\n\t}\n\twg.Wait()\n}\n"
  },
  {
    "path": "integration_test/ignored_mounts_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n)\n\nfunc (s *mountsSuite) Test_IgnoredMounts() {\n\ttype lm struct {\n\t\tname        string\n\t\tsvcPort     int\n\t\tignored     []string\n\t\texpected    []string\n\t\tnotExpected []string\n\t}\n\ttests := []lm{\n\t\t{\n\t\t\t\"no ingored volumes\",\n\t\t\t80,\n\t\t\t[]string{},\n\t\t\t[]string{\n\t\t\t\t\"var/run/secrets/kubernetes.io/serviceaccount\",\n\t\t\t\t\"var/run/secrets/datawire.io/auth\",\n\t\t\t\t\"usr/share/nginx/html\",\n\t\t\t\t\"etc/nginx/templates\",\n\t\t\t},\n\t\t\t[]string{},\n\t\t},\n\t\t{\n\t\t\t\"ignore-by-name\",\n\t\t\t80,\n\t\t\t[]string{\n\t\t\t\t\"hello-data-volume-1\",\n\t\t\t\t\"nginx-config\",\n\t\t\t},\n\t\t\t[]string{\n\t\t\t\t\"var/run/secrets/kubernetes.io/serviceaccount\",\n\t\t\t\t\"var/run/secrets/datawire.io/auth\",\n\t\t\t},\n\t\t\t[]string{\n\t\t\t\t\"usr/share/nginx/html\",\n\t\t\t\t\"etc/nginx/templates\",\n\t\t\t},\n\t\t},\n\t}\n\n\tlocalPort, cancel := itest.StartLocalHttpEchoServer(s.Context(), \"hello\")\n\tdefer cancel()\n\n\tfor _, tt := range tests {\n\t\ts.Run(tt.name, func() {\n\t\t\ttpl := struct {\n\t\t\t\tAnnotations map[string]string\n\t\t\t}{\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tannotation.InjectIgnoreVolumeMounts: strings.Join(tt.ignored, \",\"),\n\t\t\t\t},\n\t\t\t}\n\t\t\tctx := s.Context()\n\t\t\ts.ApplyTemplate(ctx, filepath.Join(\"testdata\", \"k8s\", \"hello-w-volumes.goyaml\"), &tpl)\n\t\t\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", \"hello\")\n\n\t\t\trequire := s.Require()\n\t\t\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"hello\", \"--output\", \"json\", \"--detailed-output\", \"--port\", fmt.Sprintf(\"%d:%d\", localPort, tt.svcPort))\n\t\t\tdefer itest.TelepresenceOk(ctx, \"leave\", \"hello\")\n\t\t\tvar iInfo intercept.Info\n\t\t\trequire.NoError(json.Unmarshal([]byte(stdout), &iInfo))\n\t\t\ts.CapturePodLogs(ctx, \"hello\", \"traffic-agent\", s.AppNamespace())\n\t\t\tmountPoint := iInfo.Mount.LocalDir\n\t\t\tfor _, desired := range tt.expected {\n\t\t\t\tst, err := os.Stat(filepath.Join(mountPoint, desired))\n\t\t\t\tif !s.NoErrorf(err, \"mount of %s should be successful\", desired) {\n\t\t\t\t\ts.T().FailNow()\n\t\t\t\t}\n\t\t\t\trequire.True(st.IsDir())\n\t\t\t}\n\t\t\tfor _, notDesired := range tt.notExpected {\n\t\t\t\tst, err := os.Stat(filepath.Join(mountPoint, notDesired))\n\t\t\t\tif !s.Errorf(err, \"mount of %s should not be successful\", notDesired) {\n\t\t\t\t\tclog.Infof(ctx, \"stat gave us %s %t %s\", st.Name(), st.IsDir(), st.Mode())\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "integration_test/inactive_client_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\ntype inactiveClientSuite struct {\n\titest.Suite\n\titest.NamespacePair\n\n\t// The name of the workload to intercept.\n\tsvc string\n\n\t// The time a client can be inactive before it loses its right to blocks conflicting intercepts.\n\tinactiveBlockTimeout time.Duration\n\n\t// How often the client pings the manager to keep its right to block intercepts.\n\tpingInterval time.Duration\n}\n\nfunc (s *inactiveClientSuite) SuiteName() string {\n\treturn \"InactiveClient\"\n}\n\nfunc init() {\n\titest.AddNamespacePairSuite(\"\", func(h itest.NamespacePair) itest.TestingSuite {\n\t\treturn &inactiveClientSuite{\n\t\t\tSuite:                itest.Suite{Harness: h},\n\t\t\tNamespacePair:        h,\n\t\t\tsvc:                  \"echo-easy\",\n\t\t\tinactiveBlockTimeout: 10 * time.Second,\n\t\t\tpingInterval:         2 * time.Second,\n\t\t}\n\t})\n}\n\nfunc (s *inactiveClientSuite) SetupSuite() {\n\ts.Suite.SetupSuite()\n\n\t// Default here is 10 minutes. We don't want to wait that long for the test to complete.\n\ts.TelepresenceHelmInstallOK(s.Context(), false, \"--set\", \"logLevel=trace,intercept.inactiveBlockTimeout=\"+s.inactiveBlockTimeout.String())\n\tctx := s.Context()\n\n\ts.ApplyApp(ctx, s.svc, \"deploy/\"+s.svc)\n}\n\nfunc (s *inactiveClientSuite) TearDownSuite() {\n\tctx := s.Context()\n\ts.DeleteSvcAndWorkload(ctx, \"deploy\", s.svc)\n\ts.UninstallTrafficManager(ctx, s.ManagerNamespace())\n}\n\nfunc (s *inactiveClientSuite) AmendSuiteContext(ctx context.Context) context.Context {\n\treturn itest.WithConfig(ctx, func(cfg client.Config) {\n\t\tcfg.Intercept().UseFtp = false\n\n\t\t// Ping is normally one minute, we need to be faster here.\n\t\tcfg.Grpc().PingInterval = s.pingInterval\n\t})\n}\n\nfunc (s *inactiveClientSuite) Test_ConflictOverrideInactive() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--name\", \"conflict-one\")\n\tdefer func() {\n\t\t// Clean up the successful intercept\n\t\titest.TelepresenceDisconnect(ctx, \"--use\", \"conflict-one\")\n\t}()\n\n\trequire := s.Require()\n\n\t// This test verifies that real TCP port conflicts are still properly detected\n\t// when two intercepts try to use the same local port\n\n\t// Start first intercept using port 8080\n\tstdout1, stderr1, err1 := itest.Telepresence(ctx, \"--use\", \"conflict-one\", \"intercept\", \"conflict-one\",\n\t\t\"--workload\", s.svc,\n\t\t\"--http-header\", \"x-user=adam\",\n\t\t\"--port\", \"8080:80\",\n\t\t\"--mount\", \"false\")\n\trequire.NoError(err1, \"First intercept should succeed - stderr: %s\", stderr1)\n\trequire.Contains(stdout1, \"Using Deployment\")\n\ts.CapturePodLogs(ctx, s.svc, \"traffic-agent\", s.AppNamespace())\n\n\t// Create a second client.\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--name\", \"conflict-two\")\n\tdefer func() {\n\t\t// Clean up the successful intercept\n\t\titest.TelepresenceDisconnect(ctx, \"--use\", \"conflict-two\")\n\t}()\n\n\t// Attempt second intercept using the same header. This should fail with a header conflict\n\t_, stderr2, err2 := itest.Telepresence(ctx, \"--use\", \"conflict-two\", \"intercept\", \"conflict-two\",\n\t\t\"--workload\", s.svc,\n\t\t\"--http-header\", \"x-user=adam\",\n\t\t\"--port\", \"8081:80\",\n\t\t\"--mount\", \"false\")\n\n\t// Should fail due to real TCP port conflict\n\trequire.Error(err2, \"Second intercept should fail due to header conflict\")\n\n\t// Verify it's a header conflict\n\ts.Contains(stderr2, \"header filters overlap\")\n\n\t// Sleep until the first client have lost its right to block intercepts.\n\ttime.Sleep(s.inactiveBlockTimeout + s.pingInterval)\n\t// Attempt the second intercept again. This should now succeed.\n\t_, _, err2 = itest.Telepresence(ctx, \"--use\", \"conflict-two\", \"intercept\", \"conflict-two\",\n\t\t\"--workload\", s.svc,\n\t\t\"--http-header\", \"x-user=adam\",\n\t\t\"--port\", \"8081:80\",\n\t\t\"--mount\", \"false\")\n\ts.NoError(err2, \"Second intercept should succeed when the first client is sleeping\")\n\n\t// The client that woke up should now see the intercept in an error state explaining the conflict.\n\tso, se, err := itest.Telepresence(ctx, \"--use\", \"conflict-one\", \"list\", \"--intercepts\")\n\ts.NoErrorf(err, \"Failed to list intercepts - stderr: %s\", se)\n\tif se != \"\" {\n\t\tclog.Error(ctx, se)\n\t}\n\ts.Regexp(regexp.MustCompile(`(?m)Intercept name: conflict-one\\n.*AGENT_ERROR: conflict with intercept [\\w-]+:conflict-two`), so)\n\tclog.Info(ctx, so)\n}\n\nfunc (s *inactiveClientSuite) Test_ConflictOverrideSleeping() {\n\tctx := s.Context()\n\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--name\", \"conflict-one\")\n\tdefer func() {\n\t\t// Clean up the successful intercept\n\t\titest.TelepresenceDisconnect(ctx, \"--use\", \"conflict-one\")\n\t}()\n\n\trequire := s.Require()\n\n\t// This test verifies that real TCP port conflicts are still properly detected\n\t// when two intercepts try to use the same local port\n\n\t// Start first intercept using port 8080\n\tstdout1, stderr1, err1 := itest.Telepresence(ctx, \"--use\", \"conflict-one\", \"intercept\", \"conflict-one\",\n\t\t\"--workload\", s.svc,\n\t\t\"--http-header\", \"x-user=adam\",\n\t\t\"--port\", \"8080:80\",\n\t\t\"--mount\", \"false\")\n\trequire.NoError(err1, \"First intercept should succeed - stderr: %s\", stderr1)\n\trequire.Contains(stdout1, \"Using Deployment\")\n\ts.CapturePodLogs(ctx, s.svc, \"traffic-agent\", s.AppNamespace())\n\n\t// Create a second client.\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--name\", \"conflict-two\")\n\tdefer func() {\n\t\t// Clean up the successful intercept\n\t\titest.TelepresenceDisconnect(ctx, \"--use\", \"conflict-two\")\n\t}()\n\n\t// Attempt second intercept using the same header. This should fail with a header conflict\n\t_, stderr2, err2 := itest.Telepresence(ctx, \"--use\", \"conflict-two\", \"intercept\", \"conflict-two\",\n\t\t\"--workload\", s.svc,\n\t\t\"--http-header\", \"x-user=adam\",\n\t\t\"--port\", \"8081:80\",\n\t\t\"--mount\", \"false\")\n\n\t// Should fail due to real TCP port conflict\n\trequire.Error(err2, \"Second intercept should fail due to header conflict\")\n\n\t// Verify it's a header conflict\n\ts.Contains(stderr2, \"header filters overlap\")\n\n\t// Try again, but this time put the first client to sleep.\n\ts.withSleepingClient(ctx, \"conflict-one\", func(ctx context.Context) {\n\t\t// Sleep until the first client have lost its right to block intercepts.\n\t\ttime.Sleep(s.inactiveBlockTimeout + s.pingInterval)\n\t\t// Attempt the second intercept again. This should now succeed.\n\t\t_, _, err2 = itest.Telepresence(ctx, \"--use\", \"conflict-two\", \"intercept\", \"conflict-two\",\n\t\t\t\"--workload\", s.svc,\n\t\t\t\"--http-header\", \"x-user=adam\",\n\t\t\t\"--port\", \"8081:80\",\n\t\t\t\"--mount\", \"false\")\n\t\ts.NoError(err2, \"Second intercept should succeed when the first client is sleeping\")\n\t})\n\n\t// The client that woke up should now see the intercept in an error state explaining the conflict.\n\tso, se, err := itest.Telepresence(ctx, \"--use\", \"conflict-one\", \"list\", \"--intercepts\")\n\ts.NoErrorf(err, \"Failed to list intercepts - stderr: %s\", se)\n\tif se != \"\" {\n\t\tclog.Error(ctx, se)\n\t}\n\ts.Regexp(regexp.MustCompile(`(?m)Intercept name: conflict-one\\n.*AGENT_ERROR: conflict with intercept [\\w-]+:conflict-two`), so)\n\tclog.Info(ctx, so)\n}\n\nfunc (s *inactiveClientSuite) withSleepingClient(ctx context.Context, clientName string, f func(ctx context.Context)) {\n\t// Put the client to sleep but keep its daemon info file alive. We don't want\n\t// other CLI commands to remove it when it goes stale.\n\ts.Require().NoError(itest.Run(ctx, \"docker\", \"pause\", clientName))\n\tinfoFile := clientName + \".json\"\n\tgo func() {\n\t\ts.NoError(daemon.NewUserInfoLoader(ctx).KeepInfoAlive(infoFile))\n\t}()\n\tf(ctx)\n\n\t// Unpause and restore the info file timestamp (because unpause reverts the timestamp).\n\tfullInfoFile := filepath.Join(filelocation.AppUserCacheDir(ctx), \"userd\", infoFile)\n\tst, err := os.Stat(fullInfoFile)\n\ts.NoError(err)\n\ts.NoError(itest.Run(ctx, \"docker\", \"unpause\", clientName))\n\ts.NoError(os.Chtimes(fullInfoFile, time.Time{}, st.ModTime()))\n}\n"
  },
  {
    "path": "integration_test/ingest_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ingest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\ntype ingestSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tmounts []string\n}\n\nfunc (s *ingestSuite) SuiteName() string {\n\treturn \"Ingest\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &ingestSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *ingestSuite) SetupSuite() {\n\tctx := s.Context()\n\ts.Suite.SetupSuite()\n\twg := sync.WaitGroup{}\n\twg.Add(3)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"intercept.environment.excluded={DATABASE_HOST,DATABASE_PASSWORD}\")\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\titest.ApplyAppTemplate(ctx, s.AppNamespace(), &itest.AppData{\n\t\t\tAppName: \"echo-env\",\n\t\t\tImage:   \"ghcr.io/telepresenceio/echo-server:latest\",\n\t\t\tPorts: []itest.AppPort{\n\t\t\t\t{\n\t\t\t\t\tServicePortNumber: 80,\n\t\t\t\t\tTargetPortName:    \"http\",\n\t\t\t\t\tTargetPortNumber:  8080,\n\t\t\t\t},\n\t\t\t},\n\t\t\tEnv: map[string]string{\n\t\t\t\t\"PORT\":              \"8080\",\n\t\t\t\t\"TEST\":              \"DATA\",\n\t\t\t\t\"INTERCEPT\":         \"ENV\",\n\t\t\t\t\"DATABASE_HOST\":     \"HOST_NAME\",\n\t\t\t\t\"DATABASE_PASSWORD\": \"SUPER_SECRET_PASSWORD\",\n\t\t\t},\n\t\t})\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\titest.ApplyAppTemplate(ctx, s.AppNamespace(), &itest.AppData{\n\t\t\tAppName: \"echo\",\n\t\t\tImage:   \"ghcr.io/telepresenceio/echo-server:latest\",\n\t\t\tPorts: []itest.AppPort{\n\t\t\t\t{\n\t\t\t\t\tServicePortNumber: 80,\n\t\t\t\t\tTargetPortName:    \"http\",\n\t\t\t\t\tTargetPortNumber:  8080,\n\t\t\t\t},\n\t\t\t},\n\t\t\tEnv: map[string]string{\"PORT\": \"8080\"},\n\t\t})\n\t}()\n\twg.Wait()\n}\n\nfunc (s *ingestSuite) TearDownSuite() {\n\tctx := s.Context()\n\twg := sync.WaitGroup{}\n\twg.Add(3)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts.DeleteSvcAndWorkload(ctx, \"deploy\", \"echo\")\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts.DeleteSvcAndWorkload(ctx, \"deploy\", \"echo-env\")\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts.RollbackTM(ctx)\n\t}()\n\twg.Wait()\n\tfor _, mount := range s.mounts {\n\t\tgo func() {\n\t\t\ttime.Sleep(time.Second)\n\t\t\t_ = os.RemoveAll(mount)\n\t\t}()\n\t}\n}\n\nfunc (s *ingestSuite) mountPoint() string {\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\treturn \"T:\"\n\tcase \"darwin\":\n\t\tif s.IsCI() {\n\t\t\t// Run without mounting on darwin. Apple prevents proper install of kernel extensions\n\t\t\treturn \"false\"\n\t\t}\n\t\tfallthrough\n\tdefault:\n\t\tmountPoint, err := os.MkdirTemp(\"\", \"mount-\") // Don't use the testing.Tempdir() because deletion is delayed.\n\t\ts.Require().NoError(err)\n\t\ts.mounts = append(s.mounts, mountPoint)\n\t\treturn mountPoint\n\t}\n}\n\nfunc (s *ingestSuite) Test_IngestCLI() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\tmountPoint := s.mountPoint()\n\tjs := itest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"echo-env\", \"--output\", \"json\")\n\tclog.Info(ctx, js)\n\tvar rsp ingest.Info\n\ts.Require().NoError(json.Unmarshal([]byte(js), &rsp))\n\tenv := rsp.Environment\n\ts.Empty(env[\"DATABASE_HOST\"])\n\ts.Empty(env[\"DATABASE_PASSWORD\"])\n\ts.Equal(\"DATA\", env[\"TEST\"])\n\ts.Contains(\"ENV\", env[\"INTERCEPT\"])\n\n\tif mountPoint != \"false\" {\n\t\ttestDir := filepath.Join(rsp.Mount.LocalDir, \"var\")\n\t\ts.Eventually(func() bool {\n\t\t\tst, err := os.Stat(testDir)\n\t\t\treturn err == nil && st.Mode().IsDir()\n\t\t}, 15*time.Second, 3*time.Second)\n\t}\n}\n\nfunc (s *ingestSuite) Test_IngestIngestConflict() {\n\tmountPoint := s.mountPoint()\n\tif mountPoint == \"false\" {\n\t\ts.T().Skip(\"mounts disabled on this platform\")\n\t}\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\titest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"echo-env\")\n\tso, se, err := itest.Telepresence(ctx, \"ingest\", \"--mount\", mountPoint, \"echo\")\n\ts.Require().Error(err)\n\ts.Empty(so)\n\ts.Contains(se, \"already in use by ingest\")\n}\n\nfunc (s *ingestSuite) Test_IngestInterceptConflict() {\n\tmountPoint := s.mountPoint()\n\tif mountPoint == \"false\" {\n\t\ts.T().Skip(\"mounts disabled on this platform\")\n\t}\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\titest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"echo-env\")\n\tso, se, err := itest.Telepresence(ctx, \"intercept\", \"--mount\", mountPoint, \"echo\")\n\ts.Require().Error(err)\n\ts.Empty(so)\n\ts.Contains(se, \"already in use by ingest\")\n}\n\nfunc (s *ingestSuite) Test_InterceptIngestConflict() {\n\tmountPoint := s.mountPoint()\n\tif mountPoint == \"false\" {\n\t\ts.T().Skip(\"mounts disabled on this platform\")\n\t}\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", mountPoint, \"echo-env\")\n\tso, se, err := itest.Telepresence(ctx, \"ingest\", \"--mount\", mountPoint, \"echo\")\n\ts.Require().Error(err)\n\ts.Empty(so)\n\ts.Contains(se, \"already in use by intercept\")\n}\n\nfunc (s *ingestSuite) Test_IngestRepeat() {\n\tmountPoint := s.mountPoint()\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\ti1 := itest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"echo-env\", \"--output\", \"json\")\n\ti2 := itest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"echo-env\", \"--output\", \"json\")\n\ts.Equal(i1, i2)\n}\n\nfunc (s *ingestSuite) Test_IngestFTP() {\n\tif !s.ClientVersion().EQ(version.Structured) {\n\t\ts.T().Skip(`Not part of compatibility tests. DoWithSession assumes compiled executable`)\n\t}\n\tmountPoint := filepath.Join(s.T().TempDir(), \"mnt\")\n\trq := s.Require()\n\trq.NoError(os.Mkdir(mountPoint, 0o755))\n\n\tctx := s.Context()\n\trq.NoError(s.DoWithSession(ctx, s.NewConnectRequest(ctx), func(ctx context.Context, svc connector.ConnectorServer) {\n\t\trsp, err := svc.Ingest(ctx, &connector.IngestRequest{\n\t\t\tMountPoint: mountPoint,\n\t\t\tIdentifier: &connector.IngestIdentifier{\n\t\t\t\tWorkloadName: \"echo-env\",\n\t\t\t},\n\t\t})\n\t\trq.NoError(err)\n\t\tenv := rsp.Environment\n\t\ts.Empty(env[\"DATABASE_HOST\"])\n\t\ts.Empty(env[\"DATABASE_PASSWORD\"])\n\t\ts.Equal(\"DATA\", env[\"TEST\"])\n\t\ts.Contains(\"ENV\", env[\"INTERCEPT\"])\n\n\t\ttestDir := filepath.Join(rsp.ClientMountPoint, \"var\")\n\t\ts.Eventually(func() bool {\n\t\t\tst, err := os.Stat(testDir)\n\t\t\treturn err == nil && st.Mode().IsDir()\n\t\t}, 15*time.Second, 3*time.Second)\n\n\t\t_, err = svc.LeaveIngest(ctx, &connector.IngestIdentifier{\n\t\t\tWorkloadName: \"echo-env\",\n\t\t})\n\t\trq.NoError(err)\n\t}))\n}\n\nfunc (s *ingestSuite) Test_IngestProxyVia() {\n\tctx := s.Context()\n\tcr := s.NewConnectRequest(ctx)\n\n\t// Simulate --proxy-via all=echo-easy\n\tcr.SubnetViaWorkloads = []*daemon.SubnetViaWorkload{\n\t\t{\n\t\t\tSubnet:   \"also\",\n\t\t\tWorkload: \"echo-env\",\n\t\t},\n\t\t{\n\t\t\tSubnet:   \"service\",\n\t\t\tWorkload: \"echo-env\",\n\t\t},\n\t\t{\n\t\t\tSubnet:   \"pods\",\n\t\t\tWorkload: \"echo-env\",\n\t\t},\n\t}\n\n\tctx = itest.WithConfig(ctx, func(cfg client.Config) {\n\t\t// We currently have no way to make proxy-via work with FTP, because FTP\n\t\t// sends an IP-address in a TCP message.\n\t\tcfg.Intercept().UseFtp = false\n\t})\n\n\tmountPoint := filepath.Join(s.T().TempDir(), \"mnt\")\n\trq := s.Require()\n\trq.NoError(os.Mkdir(mountPoint, 0o755))\n\trq.NoError(s.DoWithSession(ctx, cr, func(ctx context.Context, svc connector.ConnectorServer) {\n\t\trsp, err := svc.Ingest(ctx, &connector.IngestRequest{\n\t\t\tMountPoint: mountPoint,\n\t\t\tIdentifier: &connector.IngestIdentifier{\n\t\t\t\tWorkloadName: \"echo-env\",\n\t\t\t},\n\t\t})\n\t\trq.NoError(err)\n\t\tenv := rsp.Environment\n\t\ts.Empty(env[\"DATABASE_HOST\"])\n\t\ts.Empty(env[\"DATABASE_PASSWORD\"])\n\t\ts.Equal(\"DATA\", env[\"TEST\"])\n\t\ts.Contains(\"ENV\", env[\"INTERCEPT\"])\n\n\t\ttestDir := filepath.Join(rsp.ClientMountPoint, \"var\")\n\t\ts.Eventually(func() bool {\n\t\t\tst, err := os.Stat(testDir)\n\t\t\treturn err == nil && st.Mode().IsDir()\n\t\t}, 15*time.Second, 3*time.Second)\n\n\t\t_, err = svc.LeaveIngest(ctx, &connector.IngestIdentifier{\n\t\t\tWorkloadName: \"echo-env\",\n\t\t})\n\t\trq.NoError(err)\n\t}))\n}\n\nfunc (s *ingestSuite) Test_IngestWithCommand() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\tmountPoint := s.mountPoint()\n\tstdout := itest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"echo\", \"--\", \"echo\", \"test-output\")\n\ts.Contains(stdout, \"test-output\")\n}\n\nfunc (s *ingestSuite) Test_IngestWithContainerAndCommand() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\tmountPoint := s.mountPoint()\n\tstdout := itest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"--container\", \"echo\", \"echo\", \"--\", \"echo\", \"explicit-container\")\n\ts.Contains(stdout, \"explicit-container\")\n}\n\nfunc (s *ingestSuite) Test_LeaveIngestWithoutContainer() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\tmountPoint := s.mountPoint()\n\n\titest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"echo\")\n\n\titest.TelepresenceOk(ctx, \"leave\", \"echo\")\n\n\tstdout := itest.TelepresenceOk(ctx, \"list\", \"--ingests\")\n\ts.NotContains(stdout, \"echo\")\n}\n\nfunc (s *ingestSuite) Test_IngestListFormat() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\tmountPoint := s.mountPoint()\n\n\titest.TelepresenceOk(ctx, \"ingest\", \"--mount\", mountPoint, \"echo-env\")\n\n\tstdout := itest.TelepresenceOk(ctx, \"list\", \"--ingests\")\n\ts.Contains(stdout, \"echo-env\")\n\n\titest.TelepresenceOk(ctx, \"leave\", \"echo-env\")\n}\n"
  },
  {
    "path": "integration_test/inject_policy_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tlabels2 \"k8s.io/apimachinery/pkg/labels\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/labels\"\n)\n\nfunc (is *installSuite) applyPolicyApp(ctx context.Context, name, namespace string, wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\tis.T().Helper()\n\tmanifest := filepath.Join(\"testdata\", \"k8s\", name+\".yaml\")\n\tis.NoError(itest.Kubectl(ctx, namespace, \"apply\", \"-f\", manifest), \"failed to apply %s\", manifest)\n\tis.NoError(itest.RolloutStatusWait(ctx, namespace, \"deploy/\"+name))\n}\n\nfunc (is *installSuite) assertInjected(ctx context.Context, name, namespace string, present bool, wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\tis.T().Helper()\n\tout, err := itest.KubectlOut(ctx, namespace, \"get\", \"pods\", \"-l\", \"app=\"+name, \"-o\", \"jsonpath={.items.*.spec.containers[?(@.name=='traffic-agent')].image}\")\n\tis.NoError(err)\n\tn := \"tel2\"\n\tif ai := itest.GetAgentImage(ctx); ai != nil {\n\t\tn = ai.Name\n\t}\n\tn = \"/\" + n + \":\"\n\tif present {\n\t\tis.Contains(out, n)\n\t} else {\n\t\tis.NotContains(out, n)\n\t}\n}\n\nfunc (is *installSuite) injectPolicyTest(ctx context.Context, policy agentconfig.InjectPolicy) {\n\tnamespace := fmt.Sprintf(\"%s-%s\", strings.ToLower(policy.String()), is.Suffix())\n\titest.CreateNamespaces(ctx, namespace)\n\tdefer itest.DeleteNamespaces(ctx, namespace)\n\n\tctx = itest.WithNamespaces(ctx, &itest.Namespaces{\n\t\tNamespace: namespace,\n\t\tSelector:  labels.SelectorFromNames(namespace),\n\t})\n\tis.TelepresenceHelmInstallOK(ctx, false, \"--set\", \"agentInjector.injectPolicy=\"+policy.String())\n\tdefer is.UninstallTrafficManager(ctx, namespace)\n\n\tctx = itest.WithUser(ctx, namespace+\":\"+itest.TestUser)\n\titest.TelepresenceOk(ctx, \"connect\", \"--namespace\", namespace, \"--manager-namespace\", namespace)\n\tdefer itest.TelepresenceOk(ctx, \"quit\", \"-s\")\n\n\titest.TelepresenceOk(ctx, \"loglevel\", \"debug\")\n\n\twg := sync.WaitGroup{}\n\twg.Add(3)\n\tgo is.applyPolicyApp(ctx, \"pol-enabled\", namespace, &wg)\n\tgo is.applyPolicyApp(ctx, \"pol-none\", namespace, &wg)\n\tgo is.applyPolicyApp(ctx, \"pol-disabled\", namespace, &wg)\n\twg.Wait()\n\tif is.T().Failed() {\n\t\treturn\n\t}\n\n\t// No pod should have a traffic-agent at this stage, except for the pol-enabled when the policy is WhenEnabled\n\twg.Add(3)\n\tgo is.assertInjected(ctx, \"pol-enabled\", namespace, true, &wg)   // always injected in advance\n\tgo is.assertInjected(ctx, \"pol-none\", namespace, false, &wg)     // never injected in advance\n\tgo is.assertInjected(ctx, \"pol-disabled\", namespace, false, &wg) // never injected\n\twg.Wait()\n\tif is.T().Failed() {\n\t\treturn\n\t}\n\n\t// An intercept on the pol-disabled must always fail\n\twg.Add(1)\n\tgo func() {\n\t\t_, _, err := itest.Telepresence(ctx, \"intercept\", \"--mount\", \"false\", \"pol-disabled\", \"--\", \"true\")\n\t\tis.Error(err)\n\t\tis.assertInjected(ctx, \"pol-disabled\", namespace, false, &wg)\n\t}()\n\n\t// for OnDemand, an intercept on the pol-none must succeed inject the agent\n\tif policy == agentconfig.OnDemand {\n\t\twg.Add(1)\n\t\t_, _, err := itest.Telepresence(ctx, \"intercept\", \"--mount\", \"false\", \"pol-none\", \"--\", \"true\")\n\t\tis.NoError(err)\n\t\tis.assertInjected(ctx, \"pol-none\", namespace, true, &wg)\n\t}\n\twg.Wait()\n}\n\nfunc (is *installSuite) TestInjectPolicy() {\n\tfor _, policy := range []agentconfig.InjectPolicy{agentconfig.OnDemand, agentconfig.WhenEnabled} {\n\t\tis.Run(policy.String(), func() {\n\t\t\tis.injectPolicyTest(is.Context(), policy)\n\t\t})\n\t}\n}\n\nfunc (is *installSuite) applyMultipleServices(svcCount int) {\n\tis.applyOrDeleteMultipleServices(svcCount, is.ApplyTemplate)\n\t// And check that all pods receive a traffic-agent\n\tis.Eventually(func() bool {\n\t\tpods := itest.RunningPodsSelector(is.Context(), is.AppNamespace(), labels2.SelectorFromSet(map[string]string{\n\t\t\t\"multi-service-test\": \"inject\",\n\t\t}))\n\t\tclog.Infof(is.Context(), \"pod count %d, expected %d\", len(pods), svcCount)\n\t\treturn len(pods) == svcCount\n\t}, 120*time.Second, 5*time.Second)\n}\n\nfunc (is *installSuite) deleteMultipleServices(svcCount int) {\n\tis.applyOrDeleteMultipleServices(svcCount, is.DeleteTemplate)\n}\n\nfunc (is *installSuite) applyOrDeleteMultipleServices(svcCount int, applyOrDelete func(context.Context, string, any)) {\n\tctx := is.Context()\n\twg := sync.WaitGroup{}\n\twg.Add(svcCount)\n\tfor i := range svcCount {\n\t\tsvc := fmt.Sprintf(\"quote-%d\", i)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tk8s := filepath.Join(\"testdata\", \"k8s\")\n\t\t\tapplyOrDelete(ctx, filepath.Join(k8s, \"generic.goyaml\"), &itest.Generic{\n\t\t\t\tName:     svc,\n\t\t\t\tRegistry: \"datawire\",\n\t\t\t\tImage:    \"quote:0.5.0\",\n\t\t\t\tAnnotations: map[string]string{\n\t\t\t\t\tannotation.InjectTrafficAgent: \"enabled\",\n\t\t\t\t},\n\t\t\t\tLabels: map[string]string{\n\t\t\t\t\t\"multi-service-test\": \"inject\",\n\t\t\t\t},\n\t\t\t})\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc (is *installSuite) Test_MultiOnDemandInjectOnInstall() {\n\tif !(is.ManagerIsVersion(\">2.24.x\") && is.ClientIsVersion(\">2.24.x\")) {\n\t\tis.T().Skip(\"Not part of compatibility tests.\")\n\t}\n\tsvcCount := 8\n\tif runtime.GOOS != \"linux\" {\n\t\t// The GitHub runner is probably using Colima for Kubernetes and running with limited\n\t\t// resources.\n\t\tsvcCount = 4\n\t}\n\tctx := is.Context()\n\n\t// First create the pods with inject annotation\n\tis.applyMultipleServices(svcCount)\n\tdefer is.deleteMultipleServices(svcCount)\n\n\t// Then install the traffic-manager\n\tis.TelepresenceHelmInstallOK(ctx, false)\n\tdefer func() {\n\t\t// Uninstall the traffic-manager and check that all pods traffic-agent is removed\n\t\tis.UninstallTrafficManager(ctx, is.ManagerNamespace())\n\t\tis.Eventually(func() bool {\n\t\t\tras := itest.RunningPodsWithAgents(ctx, \"quote-\", is.AppNamespace())\n\t\t\tclog.Infof(ctx, \"pod with agent count %d, expected 0\", len(ras))\n\t\t\treturn len(ras) == 0\n\t\t}, 120*time.Second, 5*time.Second)\n\t}()\n\n\t// And check that all pods receive a traffic-agent\n\tis.Eventually(func() bool {\n\t\tras := itest.RunningPodsWithAgents(ctx, \"quote-\", is.AppNamespace())\n\t\tclog.Infof(ctx, \"pod with agent count %d, expected %d\", len(ras), svcCount)\n\t\treturn len(ras) == svcCount\n\t}, 120*time.Second, 5*time.Second)\n}\n\nfunc (is *installSuite) Test_MultiOnDemandInjectOnApply() {\n\tif !(is.ManagerIsVersion(\">2.24.x\") && is.ClientIsVersion(\">2.24.x\")) {\n\t\tis.T().Skip(\"Not part of compatibility tests.\")\n\t}\n\tsvcCount := 8\n\tif runtime.GOOS != \"linux\" {\n\t\t// The GitHub runner is probably using Colima for Kubernetes and running with limited\n\t\t// resources.\n\t\tsvcCount = 4\n\t}\n\tctx := is.Context()\n\n\t// First install the traffic-manager\n\tis.TelepresenceHelmInstallOK(ctx, false)\n\ttime.Sleep(3 * time.Second)\n\tdefer func() {\n\t\tis.UninstallTrafficManager(ctx, is.ManagerNamespace())\n\t\tis.Eventually(func() bool {\n\t\t\tras := itest.RunningPodsWithAgents(ctx, \"quote-\", is.AppNamespace())\n\t\t\tclog.Infof(ctx, \"pod with agent count %d, expected 0\", len(ras))\n\t\t\treturn len(ras) == 0\n\t\t}, 120*time.Second, 5*time.Second)\n\t}()\n\n\t// Then create the pods with inject annotation\n\tis.applyMultipleServices(svcCount)\n\tdefer is.deleteMultipleServices(svcCount)\n\n\t// And check that all pods receive a traffic-agent\n\tis.Require().Eventually(func() bool {\n\t\tras := itest.RunningPodsWithAgents(ctx, \"quote-\", is.AppNamespace())\n\t\tclog.Infof(ctx, \"pod with agent count %d, expected %d\", len(ras), svcCount)\n\t\treturn len(ras) == svcCount\n\t}, 120*time.Second, 5*time.Second)\n}\n"
  },
  {
    "path": "integration_test/injector_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\n// Test_InterceptOperationRestoredAfterFailingInject tests that the telepresence-agents\n// configmap is kept in sync with installed agents after errors occurs during the actual\n// injection of a traffic-agent.\n// See ticket https://github.com/telepresenceio/telepresence/issues/3441 for more info.\nfunc (s *singleServiceSuite) Test_InterceptOperationRestoredAfterFailingInject() {\n\tif s.ClientIsVersion(\"<2.22.0\") && s.ManagerIsVersion(\">=2.22.0\") {\n\t\ts.T().Skip(\"Not part of compatibility tests. Clients < 2.22.0 cannot uninstall agents with traffic-manager >= 2.22.0\")\n\t}\n\tctx := s.Context()\n\trq := s.Require()\n\n\toneContainer := func() bool {\n\t\tpods := itest.RunningPodNames(ctx, s.ServiceName(), s.AppNamespace())\n\t\tif len(pods) != 1 {\n\t\t\tclog.Infof(ctx, \"got %d pods\", len(pods))\n\t\t\treturn false\n\t\t}\n\t\tpodJSON, err := s.KubectlOut(ctx, \"get\", \"pod\", pods[0], \"--output\", \"json\")\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"unable to get pod %s: %v\", pods[0], err)\n\t\t\treturn false\n\t\t}\n\t\tvar pod core.Pod\n\t\terr = json.Unmarshal([]byte(podJSON), &pod)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"unable to parse json of pod %s: %v\", pods[0], err)\n\t\t\treturn false\n\t\t}\n\t\tnc := len(pod.Spec.Containers)\n\t\tif nc == 1 {\n\t\t\treturn true\n\t\t}\n\t\tclog.Errorf(ctx, \"pod %s has %d containers\", pods[0], nc)\n\t\treturn false\n\t}\n\n\t// Ensure that agent is uninstalled.\n\tso, se, err := itest.Telepresence(ctx, \"uninstall\", s.ServiceName())\n\t// We don't care if it succeeds, but the output and error might be of interest when debugging.\n\tclog.Debugf(ctx, \"stdout: %s, stderr %s, err: %v\", so, se, err)\n\n\trq.Eventually(oneContainer, 60*time.Second, 3*time.Second)\n\n\t// Break the TLS by temporally disabling the agent-injector service. We do this by the port of the\n\t// service that the webhook is calling.\n\twh := \"agent-injector-webhook-\" + s.ManagerNamespace()\n\tpmf := `{\"webhooks\":[{\"name\": \"agent-injector-%s.telepresence.io\", \"clientConfig\": {\"service\": {\"name\": \"agent-injector\", \"port\": %d}}}]}`\n\trq.NoError(itest.Kubectl(ctx, s.ManagerNamespace(), \"patch\", \"mutatingwebhookconfiguration\", wh,\n\t\t\"--patch\", fmt.Sprintf(pmf, s.ManagerNamespace(), 3443)))\n\n\tinjectorSvcPort := 443\n\tif s.ManagerIsVersion(\">=2.24.0\") {\n\t\tinjectorSvcPort = 8443\n\t}\n\n\t// Restore the webhook port when this test ends in case an error occurred that prevented it\n\tportRestored := false\n\tdefer func() {\n\t\tif !portRestored {\n\t\t\ts.NoError(itest.Kubectl(ctx, s.ManagerNamespace(), \"patch\", \"mutatingwebhookconfiguration\", wh,\n\t\t\t\t\"--patch\", fmt.Sprintf(pmf, s.ManagerNamespace(), injectorSvcPort)))\n\t\t}\n\t}()\n\n\t// Now try to intercept. This attempt will timeout because the agent is never injected.\n\t_, _, err = itest.Telepresence(ctx, \"intercept\", s.ServiceName(), \"--mount=false\")\n\t// Wait for the intercept call to return. It must return an error.\n\trq.Error(err)\n\n\t// Verify that the pod still has no agent\n\trq.True(oneContainer())\n\n\t// Restore mutating-webhook operation.\n\trq.NoError(itest.Kubectl(ctx, s.ManagerNamespace(), \"patch\", \"mutatingwebhookconfiguration\", wh,\n\t\t\"--patch\", fmt.Sprintf(pmf, s.ManagerNamespace(), injectorSvcPort)))\n\tportRestored = true\n\n\t// Verify that intercept works OK again.\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", s.ServiceName(), \"--mount=false\")\n\trq.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\trq.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(s.ServiceName()+`\\s*: intercepted`).MatchString(stdout)\n\t}, 12*time.Second, 3*time.Second)\n\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n}\n\n// Test_HelmUpgradeWebhookSecret tests that updating the webhook secret doesn't interfere with\n// intercept operations.\n// See https://github.com/telepresenceio/telepresence/issues/3442 for more info.\nfunc (s *singleServiceSuite) Test_HelmUpgradeWebhookSecret() {\n\tctx := s.Context()\n\trq := s.Require()\n\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", s.ServiceName(), \"--mount=false\")\n\trq.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\trq.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(s.ServiceName()+`\\s*: intercepted`).MatchString(stdout)\n\t}, 12*time.Second, 3*time.Second)\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"agentInjector.certificate.regenerate=true,agentInjector.certificate.accessMethod=watch,logLevel=debug\")\n\tdefer s.RollbackTM(ctx)\n\ttime.Sleep(5 * time.Second)\n\n\t// Check that the intercept is still active\n\tst := itest.TelepresenceStatusOk(ctx)\n\trq.Len(st.UserDaemon.Intercepts, 1)\n\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n\n\t// Uninstall the agent again. We want to be sure that the webhook kicks in to inject it once\n\t// we intercept.\n\tfunc() {\n\t\tdefer func() {\n\t\t\t// Restore original user\n\t\t\titest.TelepresenceDisconnectOk(ctx)\n\t\t\ts.TelepresenceConnect(ctx)\n\t\t}()\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\ts.TelepresenceConnect(itest.WithUser(ctx, \"default\"))\n\t\titest.TelepresenceOk(ctx, \"uninstall\", s.ServiceName())\n\t}()\n\tstdout = itest.TelepresenceOk(ctx, \"intercept\", s.ServiceName(), \"--mount=false\")\n\trq.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\trq.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(s.ServiceName()+`\\s*: intercepted`).MatchString(stdout)\n\t}, 12*time.Second, 3*time.Second)\n\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n}\n\n// Test_HelmUpgradeMountedWebhookSecret tests that updating the webhook secret does interfere with\n// intercept operations.\nfunc (s *singleServiceSuite) Test_HelmUpgradeMountedWebhookSecret() {\n\tif !(s.ManagerIsVersion(\">2.24.x\") && s.ClientIsVersion(\">2.24.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility tests. Uninterrupted intercepts was implemented in 2.25.0\")\n\t}\n\tctx := s.Context()\n\trq := s.Require()\n\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", s.ServiceName(), \"--mount=false\")\n\trq.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\trq.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(s.ServiceName()+`\\s*: intercepted`).MatchString(stdout)\n\t}, 12*time.Second, 3*time.Second)\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"agentInjector.certificate.regenerate=true,agentInjector.certificate.accessMethod=mount,logLevel=debug\")\n\ttime.Sleep(5 * time.Second)\n\tdefer func() {\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\ts.RollbackTM(context.WithoutCancel(ctx))\n\t\ttime.Sleep(5 * time.Second)\n\t\ts.TelepresenceConnect(ctx)\n\t}()\n\n\t// Using accessMethod=mount will restart the traffic-manager, but the intercept should still be active.\n\tst := itest.TelepresenceStatusOk(ctx)\n\trq.Len(st.UserDaemon.Intercepts, 1)\n\n\t// Uninstall the agent again. We want to be sure that the webhook kicks in to inject it once\n\t// we intercept.\n\tfunc() {\n\t\tdefer func() {\n\t\t\t// Restore original user\n\t\t\titest.TelepresenceDisconnectOk(ctx)\n\t\t\ts.TelepresenceConnect(ctx)\n\t\t}()\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\ts.TelepresenceConnect(itest.WithUser(ctx, \"default\"))\n\t\titest.TelepresenceOk(ctx, \"uninstall\", s.ServiceName())\n\t}()\n\tstdout = itest.TelepresenceOk(ctx, \"intercept\", s.ServiceName(), \"--mount=false\")\n\trq.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\trq.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(s.ServiceName()+`\\s*: intercepted`).MatchString(stdout)\n\t}, 12*time.Second, 3*time.Second)\n\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n}\n"
  },
  {
    "path": "integration_test/install_test.go",
    "content": "package integration_test\n\nimport (\n\t\"archive/tar\"\n\t\"compress/gzip\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"helm.sh/helm/v3/pkg/action\"\n\t\"helm.sh/helm/v3/pkg/cli/values\"\n\t\"helm.sh/helm/v3/pkg/getter\"\n\trbac \"k8s.io/api/rbac/v1\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/helm\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nconst ManagerAppName = agentconfig.ManagerAppName\n\ntype installSuite struct {\n\titest.Suite\n\titest.NamespacePair\n}\n\nfunc (is *installSuite) SuiteName() string {\n\treturn \"Install\"\n}\n\nfunc init() {\n\titest.AddNamespacePairSuite(\"-install\", func(h itest.NamespacePair) itest.TestingSuite {\n\t\treturn &installSuite{Suite: itest.Suite{Harness: h}, NamespacePair: h}\n\t})\n}\n\nfunc getHelmConfig(ctx context.Context, clientGetter genericclioptions.RESTClientGetter, namespace string) (*action.Configuration, error) {\n\thelmConfig := &action.Configuration{}\n\terr := helmConfig.Init(clientGetter, namespace, \"secrets\", func(format string, args ...any) {\n\t\tctx := clog.With(ctx, \"source\", \"helm\")\n\t\tclog.Infof(ctx, format, args...)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn helmConfig, nil\n}\n\nfunc (is *installSuite) AmendSuiteContext(ctx context.Context) context.Context {\n\tif !is.ManagerVersion().EQ(is.ClientVersion()) {\n\t\t// Need to use the built executable because the client version doesn't handle the --version flag.\n\t\texe, _ := is.Executable()\n\t\tctx = itest.WithExecutable(ctx, exe)\n\t}\n\treturn ctx\n}\n\nfunc (is *installSuite) Test_UpgradeRetainsValues() {\n\tctx := is.Context()\n\trq := is.Require()\n\tis.TelepresenceHelmInstallOK(ctx, false, \"--set\", \"logLevel=debug\")\n\tdefer is.UninstallTrafficManager(ctx, is.ManagerNamespace())\n\n\tkc := is.cluster(ctx, \"\", is.ManagerNamespace())\n\thelmConfig, err := getHelmConfig(kc, kc.Kubeconfig, is.ManagerNamespace())\n\trq.NoError(err)\n\n\tgetValues := func() (map[string]any, error) {\n\t\treturn action.NewGetValues(helmConfig).Run(agentconfig.ManagerAppName)\n\t}\n\tcontainsKey := func(m map[string]any, key string) bool {\n\t\t_, ok := m[key]\n\t\treturn ok\n\t}\n\n\toldValues, err := getValues()\n\trq.NoError(err)\n\targs := []string{\"helm\", \"upgrade\", \"--namespace\", is.ManagerNamespace()}\n\tif !is.ManagerVersion().EQ(version.Structured) {\n\t\targs = append(args, \"--version\", is.ManagerVersion().String())\n\t}\n\n\tis.Run(\"default reuse-values\", func() {\n\t\titest.TelepresenceOk(is.Context(), args...)\n\t\tnewValues, err := getValues()\n\t\tif is.NoError(err) {\n\t\t\tis.Equal(oldValues, newValues)\n\t\t}\n\t})\n\n\tis.Run(\"default reset-values\", func() {\n\t\t// Setting a value means that the default behavior is to reset old values.\n\t\titest.TelepresenceOk(is.Context(), append(args, \"--set\", \"apiPort=8765\")...)\n\t\tnewValues, err := getValues()\n\t\tif is.NoError(err) {\n\t\t\tis.Equal(8765.0, newValues[\"apiPort\"])\n\t\t\tis.False(containsKey(newValues, \"logLevel\")) // Should be back at default\n\t\t}\n\t})\n\n\tis.Run(\"explicit reuse-values\", func() {\n\t\t// Set new value and enforce merge with of old values.\n\t\titest.TelepresenceOk(is.Context(), append(args, \"--set\", \"logLevel=debug\", \"--reuse-values\")...)\n\t\tnewValues, err := getValues()\n\t\tif is.NoError(err) {\n\t\t\tis.Equal(8765.0, newValues[\"apiPort\"])\n\t\t\tis.Equal(\"debug\", newValues[\"logLevel\"])\n\t\t}\n\t})\n\n\tis.Run(\"explicit reset-values\", func() {\n\t\t// Enforce reset of old values.\n\t\titest.TelepresenceOk(is.Context(), append(args, \"--reset-values\")...)\n\t\tnewValues, err := getValues()\n\t\tif is.NoError(err) {\n\t\t\tis.False(containsKey(newValues, \"apiPort\"))  // Should be back at default\n\t\t\tis.False(containsKey(newValues, \"logLevel\")) // Should be back at default\n\t\t}\n\t})\n}\n\nfunc (is *installSuite) Test_HelmTemplateInstall() {\n\tif !(is.ManagerVersion().EQ(version.Structured) && is.ClientVersion().EQ(version.Structured)) {\n\t\tis.T().Skip(\"Not part of compatibility tests. PackageHelmChart assumes current version.\")\n\t}\n\tctx := is.Context()\n\trequire := is.Require()\n\n\tchart, err := is.PackageHelmChart(ctx)\n\trequire.NoError(err)\n\tvalues := is.GetSetArgsForHelm(ctx, map[string]any{\n\t\t\"clientRbac.create\": true,\n\t\t\"clientRbac.subjects\": []rbac.Subject{{\n\t\t\tKind:      \"ServiceAccount\",\n\t\t\tName:      itest.TestUser,\n\t\t\tNamespace: is.ManagerNamespace(),\n\t\t}},\n\t\t\"managerRbac.create\": true,\n\t}, false)\n\trequire.NoError(err)\n\tvalues = append([]string{\"template\", agentconfig.ManagerAppName, chart, \"-n\", is.ManagerNamespace()}, values...)\n\tmanifest, err := itest.Output(ctx, \"helm\", values...)\n\trequire.NoError(err)\n\tout := clog.StdLogger(ctx, slog.LevelInfo).Writer()\n\tlogCtx := dos.WithStdout(dos.WithStderr(ctx, out), out)\n\trequire.NoError(itest.Kubectl(dos.WithStdin(logCtx, strings.NewReader(manifest)), \"\", \"apply\", \"-f\", \"-\"))\n\tdefer func() {\n\t\t// Sometimes the traffic-agents configmap gets wiped, causing the delete command to fail, hence we don't require.NoError\n\t\t_ = itest.Kubectl(dos.WithStdin(logCtx, strings.NewReader(manifest)), \"\", \"delete\", \"-f\", \"-\")\n\t}()\n\trequire.NoError(itest.RolloutStatusWait(ctx, is.ManagerNamespace(), \"deploy/\"+agentconfig.ManagerAppName))\n\tis.CapturePodLogs(ctx, agentconfig.ManagerAppName, \"\", is.ManagerNamespace())\n\tstdout := is.TelepresenceConnect(ctx)\n\tis.Contains(stdout, \"Connected to context\")\n\titest.TelepresenceQuitOk(ctx)\n}\n\nfunc (is *installSuite) Test_FindTrafficManager_notPresent() {\n\tkc := is.cluster(is.Context(), \"\", is.ManagerNamespace()) // ensure that k8sapi is initialized\n\n\tsv := version.Version\n\tversion.Version = \"v0.0.0-bogus\"\n\tdefer func() { version.Version = sv }()\n\n\t_, err := k8sapi.GetDeployment(kc, ManagerAppName, is.ManagerNamespace())\n\tis.Error(err, \"expected find to not find traffic-manager deployment\")\n}\n\nfunc (is *installSuite) Test_EnsureManager_toleratesFailedInstall() {\n\tif !(is.ManagerVersion().EQ(version.Structured) && is.ClientVersion().EQ(version.Structured)) {\n\t\tis.T().Skip(\"Not part of compatibility tests.\")\n\t}\n\trequire := is.Require()\n\tctx := is.Context()\n\n\tsv := version.Version\n\tversion.Version = \"v0.0.0-bogus\"\n\trestoreVersion := func() { version.Version = sv }\n\n\t// We'll call this further down, but defer it to prevent polluting other tests if we don't leave this function gracefully\n\tdefer restoreVersion()\n\tdefer is.UninstallTrafficManager(ctx, is.ManagerNamespace())\n\n\tfailCtx := itest.WithConfig(ctx, func(cfg client.Config) {\n\t\tcfg.Timeouts().PrivateHelm = 20 * time.Second // Give it time to discover the ImagePullbackOff error\n\t})\n\n\tkc := is.cluster(failCtx, \"\", is.ManagerNamespace())\n\terr := ensureTrafficManager(kc)\n\trequire.Error(err)\n\tclog.Infof(ctx, \"Got expected install failure: %v\", err)\n\trestoreVersion()\n\n\tokCtx := itest.WithConfig(ctx, func(cfg client.Config) {\n\t\tcfg.Timeouts().PrivateHelm = 20 * time.Second // Time to wait before pending state makes us assume it's stuck.\n\t})\n\tkc = is.cluster(okCtx, \"\", is.ManagerNamespace())\n\tif !is.Eventually(func() bool {\n\t\terr = ensureTrafficManager(kc)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"ensureTrafficManager failed: %v\", err)\n\t\t}\n\t\treturn err == nil\n\t}, time.Minute, 5*time.Second) {\n\t\tis.Fail(fmt.Sprintf(\"Unable to install proper manager after failed install: %v\", err))\n\t}\n}\n\nfunc (is *installSuite) Test_RemoveManager_canUninstall() {\n\tif !(is.ManagerVersion().EQ(version.Structured) && is.ClientVersion().EQ(version.Structured)) {\n\t\tis.T().Skip(\"Not part of compatibility tests.\")\n\t}\n\trequire := is.Require()\n\tctx := is.Context()\n\tkc := is.cluster(ctx, \"\", is.ManagerNamespace())\n\n\trequire.NoError(ensureTrafficManager(kc))\n\trequire.NoError(helm.DeleteTrafficManager(ctx, kc.Kubeconfig, k8s.GetManagerNamespace(kc), true, &helm.Request{}))\n\t// We want to make sure that we can re-install the manager after it's been uninstalled,\n\t// so try to ensureManager again.\n\trequire.NoError(ensureTrafficManager(kc))\n\t// Uninstall the manager one last time -- this should behave the same way as the previous uninstall\n\trequire.NoError(helm.DeleteTrafficManager(kc, kc.Kubeconfig, k8s.GetManagerNamespace(kc), true, &helm.Request{}))\n}\n\nfunc (is *installSuite) Test_No_Upgrade() {\n\tif !(is.ManagerVersion().EQ(version.Structured) && is.ClientVersion().EQ(version.Structured)) {\n\t\tis.T().Skip(\"Not part of compatibility tests.\")\n\t}\n\tctx := is.Context()\n\trequire := is.Require()\n\tkc := is.cluster(ctx, \"\", is.ManagerNamespace())\n\n\tdefer is.UninstallTrafficManager(kc, is.ManagerNamespace())\n\t// first install\n\trequire.NoError(ensureTrafficManager(kc))\n\n\t// errors and asks for telepresence upgrade\n\trequire.Error(ensureTrafficManager(kc))\n\n\t// using upgrade and --values replaces TM with values\n\thelmValues := filepath.Join(\"testdata\", \"routing-values.yaml\")\n\topts := values.Options{ValueFiles: []string{helmValues}}\n\tvp, err := opts.MergeValues(getter.Providers{})\n\trequire.NoError(err)\n\tjvp, err := json.Marshal(vp)\n\trequire.NoError(err)\n\n\trequire.NoError(helm.EnsureTrafficManager(kc, kc.Kubeconfig, k8s.GetManagerNamespace(kc), &helm.Request{\n\t\tType:       helm.Upgrade,\n\t\tValuesJson: jvp,\n\t}))\n}\n\nfunc (is *installSuite) Test_findTrafficManager_differentNamespace_present() {\n\tif !(is.ManagerVersion().EQ(version.Structured) && is.ClientVersion().EQ(version.Structured)) {\n\t\tis.T().Skip(\"Not part of compatibility tests.\")\n\t}\n\tctx := is.Context()\n\tcustomNamespace := fmt.Sprintf(\"custom-%d\", os.Getpid())\n\titest.CreateNamespaces(ctx, customNamespace)\n\tdefer itest.DeleteNamespaces(ctx, customNamespace)\n\tdefer is.UninstallTrafficManager(ctx, customNamespace)\n\tctx = itest.WithKubeConfigExtension(ctx, func(cluster *api.Cluster) map[string]any {\n\t\treturn map[string]any{\"manager\": map[string]string{\"namespace\": customNamespace}}\n\t})\n\tis.findTrafficManagerPresent(ctx, \"extra\", customNamespace)\n}\n\nfunc (is *installSuite) findTrafficManagerPresent(ctx context.Context, context, namespace string) {\n\tkc := is.cluster(ctx, context, namespace)\n\trequire := is.Require()\n\trequire.NoError(ensureTrafficManager(kc))\n\trequire.Eventually(func() bool {\n\t\tdep, err := k8sapi.GetDeployment(kc, ManagerAppName, namespace)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tv := strings.TrimPrefix(version.Version, \"v\")\n\t\timg := dep.GetPodTemplate().Spec.Containers[0].Image\n\t\tclog.Infof(ctx, \"traffic-manager image %s, our version %s\", img, v)\n\t\treturn strings.Contains(img, v)\n\t}, 10*time.Second, 2*time.Second, \"traffic-manager deployment not found\")\n}\n\nfunc (is *installSuite) cluster(ctx context.Context, context, managerNamespace string) *k8s.Cluster {\n\tcluster, err := is.GetK8SCluster(ctx, context, managerNamespace)\n\tis.Require().NoError(err)\n\treturn cluster\n}\n\nfunc ensureTrafficManager(kc *k8s.Cluster) error {\n\treturn helm.EnsureTrafficManager(\n\t\tkc,\n\t\tkc.Kubeconfig,\n\t\tk8s.GetManagerNamespace(kc),\n\t\t&helm.Request{Type: helm.Install})\n}\n\nfunc unTgz(ctx context.Context, srcTgz, dstPath string) error {\n\trd, err := os.Open(srcTgz)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rd.Close()\n\n\terr = dos.MkdirAll(ctx, dstPath, 0o755)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tzrd, err := gzip.NewReader(rd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsrc := tar.NewReader(zrd)\n\tfor {\n\t\theader, err := src.Next()\n\t\tif err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\tdst := dstPath + \"/\" + header.Name\n\t\tmode := os.FileMode(header.Mode)\n\t\tswitch header.Typeflag {\n\t\tcase tar.TypeDir:\n\t\t\terr = dos.MkdirAll(ctx, dst, mode)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase tar.TypeReg:\n\t\t\terr = dos.MkdirAll(ctx, filepath.Dir(dst), 0o755)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tw, err := dos.OpenFile(ctx, dst, os.O_CREATE|os.O_WRONLY, mode)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t_, err = io.Copy(w, src)\n\t\t\t_ = w.Close()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unable to untar type : %c in file %s\", header.Typeflag, header.Name)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (is *installSuite) Test_HelmSubChart() {\n\tif runtime.GOOS == \"windows\" || !(is.ManagerVersion().EQ(version.Structured) && is.ClientVersion().EQ(version.Structured)) {\n\t\tis.T().Skip(\"Not part of compatibility tests. Need forward slashes in path, and PackageHelmChart assumes current version.\")\n\t}\n\tctx := is.Context()\n\trequire := is.Require()\n\n\tt := is.T()\n\tsubChart, err := is.PackageHelmChart(ctx)\n\trequire.NoError(err)\n\n\tbase := t.TempDir()\n\trequire.NoError(unTgz(ctx, subChart, filepath.Join(base, \"charts\")))\n\n\tchart := fmt.Sprintf(`apiVersion: v2\ndependencies:\n  - name: telepresence-oss\n    registry: ../charts/telepresence-oss\n    version: %s\n    condition: enabled\ndescription: Helm chart to deploy telepresence\nname: parent\nversion: 1.0.0`, is.ClientVersion())\n\n\tvals := is.GetSetArgsForHelm(ctx, map[string]any{\n\t\t\"global\": map[string]any{\n\t\t\t\"some-string\": \"value\",\n\t\t\t\"some-obj\": map[string]any{\n\t\t\t\t\"foo\": \"bar\",\n\t\t\t},\n\t\t\t\"some-bool\": true,\n\t\t},\n\t\t\"telepresence-oss\": map[string]any{\n\t\t\t\"clientRbac\": map[string]any{\n\t\t\t\t\"create\": true,\n\t\t\t\t\"subjects\": []rbac.Subject{\n\t\t\t\t\t{\n\t\t\t\t\t\tKind:      \"ServiceAccount\",\n\t\t\t\t\t\tName:      itest.TestUser,\n\t\t\t\t\t\tNamespace: is.ManagerNamespace(),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, false)\n\trequire.NoError(dos.WriteFile(ctx, filepath.Join(base, \"Chart.yaml\"), []byte(chart), 0o644))\n\n\tvals = append([]string{\"template\", \"parent\", base, \"-n\", is.ManagerNamespace()}, vals...)\n\tso, err := itest.Output(ctx, \"helm\", vals...)\n\trequire.NoError(err)\n\trequire.Contains(so, \"# Source: parent/charts/telepresence-oss/templates/clientRbac/connect.yaml\")\n\trequire.Contains(so, \"name: \"+itest.TestUser)\n}\n"
  },
  {
    "path": "integration_test/integration_test.go",
    "content": "package integration_test\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\nfunc Test_Integration(t *testing.T) {\n\tossRoot, err := filepath.Abs(\".\")\n\tif err != nil {\n\t\tt.Fatalf(\"unable to get absolute path of .oss: %v\", err)\n\t}\n\titest.RunTests(itest.TestContext(t, ossRoot, filepath.Dir(ossRoot)))\n}\n"
  },
  {
    "path": "integration_test/intercept_env_test.go",
    "content": "package integration_test\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype interceptEnvSuite struct {\n\titest.Suite\n\titest.NamespacePair\n}\n\nfunc (s *interceptEnvSuite) SuiteName() string {\n\treturn \"InterceptEnv\"\n}\n\nfunc init() {\n\titest.AddNamespacePairSuite(\"\", func(h itest.NamespacePair) itest.TestingSuite {\n\t\treturn &interceptEnvSuite{Suite: itest.Suite{Harness: h}, NamespacePair: h}\n\t})\n}\n\nfunc (s *interceptEnvSuite) Test_ExcludeVariables() {\n\t// given\n\tctx := s.Context()\n\ts.TelepresenceHelmInstallOK(ctx, false, \"--set\", \"intercept.environment.excluded={DATABASE_HOST,DATABASE_PASSWORD}\")\n\tdefer s.UninstallTrafficManager(ctx, s.ManagerNamespace())\n\n\ts.ApplyApp(ctx, \"echo_with_env\", \"deploy/echo-easy\")\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", \"echo-easy\")\n\n\thelloEnv := filepath.Join(s.T().TempDir(), \"echo.env\")\n\n\t// when\n\ts.TelepresenceConnect(ctx)\n\titest.TelepresenceOk(ctx, \"intercept\", \"echo-easy\", \"--env-file\", helloEnv)\n\n\t// then\n\tvar file string\n\ts.Require().Eventually(func() bool {\n\t\tif dt, err := os.ReadFile(helloEnv); err == nil {\n\t\t\tfile = string(dt)\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}, 5*time.Second, 1*time.Second)\n\n\ts.NotContains(file, \"DATABASE_HOST\")\n\ts.NotContains(file, \"DATABASE_PASSWORD\")\n\ts.Contains(file, \"TEST=DATA\")\n\ts.Contains(file, \"INTERCEPT=ENV\")\n}\n"
  },
  {
    "path": "integration_test/intercept_flags_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n)\n\ntype interceptFlagSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tserviceName string\n}\n\nfunc (s *interceptFlagSuite) SuiteName() string {\n\treturn \"InterceptFlag\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"-intercept-flag\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &interceptFlagSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *interceptFlagSuite) SetupSuite() {\n\tif s.IsCI() && runtime.GOOS == \"darwin\" {\n\t\ts.T().Skip(\"Mount tests don't run on darwin due to macFUSE issues\")\n\t\treturn\n\t}\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\ts.serviceName = \"hello\"\n\ts.ApplyTemplate(ctx, filepath.Join(\"testdata\", \"k8s\", \"hello-w-volumes.goyaml\"), nil)\n\ts.TelepresenceConnect(ctx)\n}\n\nfunc (s *interceptFlagSuite) TearDownSuite() {\n\tctx := s.Context()\n\titest.TelepresenceQuitOk(ctx)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", s.serviceName)\n}\n\n// Test_ContainerReplace tests that:\n//\n//   - Two containers in a pod can be intercepted in sequence. One with replace, and one without.\n//   - Containers can be intercepted interchangeably with or without --replace\n//   - Volumes are mounted\n//   - Intercept responses are produced from the intercept handlers\n//   - Responses after the intercept ends are from the cluster\nfunc (s *interceptFlagSuite) Test_ContainerReplace() {\n\tctx := s.Context()\n\n\tconst (\n\t\tn1 = \"container_replaced\"\n\t\tc1 = \"hello-container-1\"\n\t\tn2 = \"container_kept\"\n\t\tc2 = \"hello-container-2\"\n\t)\n\n\tlocalPort1, cancel1 := itest.StartLocalHttpEchoServer(ctx, n1)\n\tdefer cancel1()\n\n\tlocalPort2, cancel2 := itest.StartLocalHttpEchoServer(ctx, n2)\n\tdefer cancel2()\n\n\ttests := []struct {\n\t\tname         string\n\t\ticeptName    string\n\t\tappContainer string\n\t\treplace      bool\n\t\tlocalPort    int\n\t\tport         uint16\n\t}{\n\t\t{\n\t\t\tname:         n1,\n\t\t\ticeptName:    n1,\n\t\t\tappContainer: c1,\n\t\t\treplace:      true,\n\t\t\tlocalPort:    localPort1,\n\t\t\tport:         80,\n\t\t},\n\t\t{\n\t\t\tname:         n2,\n\t\t\ticeptName:    n2,\n\t\t\tappContainer: c2,\n\t\t\treplace:      false,\n\t\t\tlocalPort:    localPort2,\n\t\t\tport:         81,\n\t\t},\n\t\t{\n\t\t\tname:         \"container_replace_kept\",\n\t\t\ticeptName:    n1,\n\t\t\tappContainer: c1,\n\t\t\treplace:      false,\n\t\t\tlocalPort:    localPort1,\n\t\t\tport:         80,\n\t\t},\n\t\t{\n\t\t\tname:         \"container_kept_replaced\",\n\t\t\ticeptName:    n2,\n\t\t\tappContainer: c2,\n\t\t\treplace:      true,\n\t\t\tlocalPort:    localPort2,\n\t\t\tport:         81,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\ts.Run(tt.name, func() {\n\t\t\tctx := s.Context()\n\t\t\texpectedOutput := regexp.MustCompile(tt.iceptName + ` from intercept at`)\n\t\t\targs := []string{\"intercept\"}\n\t\t\tif tt.replace {\n\t\t\t\targs = append(args, \"--replace\")\n\t\t\t}\n\t\t\targs = append(args, \"--port\", fmt.Sprintf(\"%d:%d\", tt.localPort, tt.port), \"--output\", \"json\", \"--detailed-output\", \"--workload\", s.serviceName, tt.iceptName)\n\t\t\tjsOut := itest.TelepresenceOk(ctx, args...)\n\t\t\tagentCaptureCtx, agentCaptureCancel := context.WithCancel(ctx)\n\t\t\ts.CapturePodLogs(agentCaptureCtx, s.serviceName, \"traffic-agent\", s.AppNamespace())\n\n\t\t\tif !s.ManagerIsVersion(\">2.21.x\") {\n\t\t\t\t// Circumvent bug in 2.21.x\n\t\t\t\tdefer func() {\n\t\t\t\t\t_, _, err := itest.Telepresence(ctx, \"uninstall\", s.serviceName)\n\t\t\t\t\ts.NoError(err)\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\tdefer func() {\n\t\t\t\tagentCaptureCancel()\n\t\t\t\titest.TelepresenceOk(ctx, \"leave\", tt.iceptName)\n\t\t\t\ts.CapturePodLogs(ctx, s.serviceName, tt.appContainer, s.AppNamespace())\n\t\t\t\ts.Eventually(func() bool {\n\t\t\t\t\tout, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"1\", iputil.JoinHostPort(s.serviceName, tt.port))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif !expectedOutput.MatchString(out) {\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t\tclog.Info(ctx, out)\n\t\t\t\t\treturn false\n\t\t\t\t}, 1*time.Minute, 6*time.Second)\n\t\t\t}()\n\n\t\t\tvar ii intercept.Info\n\t\t\trequire := s.Require()\n\t\t\trequire.NoError(json.Unmarshal([]byte(jsOut), &ii))\n\t\t\trequire.Equal(ii.Name, tt.iceptName)\n\n\t\t\t// Ensure that all directories are mounted.\n\t\t\trequire.NotNil(ii.Mount)\n\t\t\tmounts := ii.Mount.Mounts\n\t\t\trequire.True(len(mounts) > 2)\n\t\t\tclog.Infof(ctx, \"Mounts = %v\", mounts)\n\t\t\trequire.Eventually(func() bool {\n\t\t\t\tfor mount := range mounts {\n\t\t\t\t\tst, err := os.Stat(filepath.Join(ii.Mount.LocalDir, mount))\n\t\t\t\t\tif !(err == nil && st.IsDir()) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t}, 10*time.Second, 2*time.Second)\n\n\t\t\trequire.Eventually(func() bool {\n\t\t\t\tout, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"1\", iputil.JoinHostPort(s.serviceName, tt.port))\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif expectedOutput.MatchString(out) {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tclog.Info(ctx, out)\n\t\t\t\treturn false\n\t\t\t}, 1*time.Minute, 6*time.Second)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "integration_test/intercept_localhost_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/routing\"\n)\n\ntype interceptLocalhostSuite struct {\n\titest.Suite\n\titest.SingleService\n\tcancelLocal  context.CancelFunc\n\tdefaultRoute *routing.Route\n\tport         int\n}\n\nfunc (s *interceptLocalhostSuite) SuiteName() string {\n\treturn \"InterceptLocalhost\"\n}\n\nfunc init() {\n\titest.AddSingleServiceSuite(\"\", \"echo\", func(h itest.SingleService) itest.TestingSuite {\n\t\treturn &interceptLocalhostSuite{Suite: itest.Suite{Harness: h}, SingleService: h}\n\t})\n}\n\nfunc (s *interceptLocalhostSuite) SetupSuite() {\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\tvar err error\n\ts.defaultRoute, err = routing.DefaultRoute(ctx)\n\ts.Require().NoError(err)\n\tclog.Infof(ctx, \"ip: %s: route: %s\", s.defaultRoute.LocalIP, s.defaultRoute)\n\ts.port, s.cancelLocal = itest.StartLocalHttpEchoServerWithAddr(ctx, s.ServiceName(), net.JoinHostPort(s.defaultRoute.LocalIP.String(), \"0\"), nil)\n}\n\nfunc (s *interceptLocalhostSuite) TearDownSuite() {\n\ts.cancelLocal()\n}\n\nfunc (s *interceptLocalhostSuite) TestIntercept_WithCustomLocalhost() {\n\tctx := s.Context()\n\tdoRequest := func(ctx context.Context, host, port string) error {\n\t\tctx, cancel := context.WithTimeout(ctx, 1*time.Second)\n\t\tdefer cancel()\n\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"http://%s/\", net.JoinHostPort(host, port)), nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tresp, err := http.DefaultClient.Do(req)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer resp.Body.Close()\n\t\t// If there was a response, make sure it's a 200\n\t\ts.Require().Equal(http.StatusOK, resp.StatusCode)\n\t\treturn nil\n\t}\n\t// Make sure the IP address we think will respond is actually gonna respond, and that localhost won't\n\ts.Require().NoError(doRequest(ctx, s.defaultRoute.LocalIP.String(), strconv.Itoa(s.port)))\n\ts.Require().Error(doRequest(ctx, \"127.0.0.1\", strconv.Itoa(s.port)))\n\n\t// Run the intercept\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", s.ServiceName(), \"--port\", strconv.Itoa(s.port), \"--address\", s.defaultRoute.LocalIP.String())\n\tdefer itest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n\n\ts.Require().Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\titest.PingInterceptedEchoServer(ctx, s.ServiceName(), \"80\")\n}\n"
  },
  {
    "path": "integration_test/intercept_mount_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n)\n\ntype interceptMountSuite struct {\n\titest.Suite\n\titest.SingleService\n\tmountPoint  string\n\tcancelLocal context.CancelFunc\n}\n\nfunc (s *interceptMountSuite) SuiteName() string {\n\treturn \"InterceptMount\"\n}\n\nfunc init() {\n\titest.AddSingleServiceSuite(\"\", \"echo\", func(h itest.SingleService) itest.TestingSuite {\n\t\treturn &interceptMountSuite{Suite: itest.Suite{Harness: h}, SingleService: h}\n\t})\n}\n\nfunc (s *interceptMountSuite) SetupSuite() {\n\tif s.IsCI() && runtime.GOOS == \"darwin\" {\n\t\ts.T().Skip(\"Mount tests don't run on darwin due to macFUSE issues\")\n\t\treturn\n\t}\n\ts.Suite.SetupSuite()\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\ts.mountPoint = \"T:\"\n\tdefault:\n\t\tvar err error\n\t\ts.mountPoint, err = os.MkdirTemp(\"\", \"mount-\") // Don't use the itest.Tempdir() because deletion is delayed.\n\t\ts.Require().NoError(err)\n\t}\n\tctx := s.Context()\n\tvar port int\n\tport, s.cancelLocal = itest.StartLocalHttpEchoServer(ctx, s.ServiceName())\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", s.ServiceName(), \"--mount\", s.mountPoint, \"--port\", strconv.Itoa(port))\n\ts.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\ts.CapturePodLogs(ctx, \"echo\", \"traffic-agent\", s.AppNamespace())\n}\n\nfunc (s *interceptMountSuite) TearDownSuite() {\n\tctx := s.Context()\n\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n\ts.cancelLocal()\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && !strings.Contains(stdout, s.ServiceName()+\": intercepted\")\n\t}, 10*time.Second, time.Second)\n\n\tif runtime.GOOS != \"windows\" {\n\t\t// Delay the deletion of the mount point so that it is properly unmounted before it's removed.\n\t\tgo func() {\n\t\t\ttime.Sleep(2 * time.Second)\n\t\t\t_ = os.RemoveAll(s.mountPoint)\n\t\t}()\n\t}\n}\n\nfunc (s *interceptMountSuite) Test_InterceptMount() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(s.ServiceName()+`\\s*: intercepted`).MatchString(stdout)\n\t}, 10*time.Second, time.Second)\n\n\ttime.Sleep(200 * time.Millisecond) // List is really fast now, so give the mount some time to become effective\n\tst, err := os.Stat(s.mountPoint)\n\trequire.NoError(err, \"Stat on <mount point> failed\")\n\trequire.True(st.IsDir(), \"Mount point is not a directory\")\n\tst, err = os.Stat(filepath.Join(s.mountPoint, \"var\"))\n\trequire.NoError(err, \"Stat on <mount point>/var failed\")\n\trequire.True(st.IsDir(), \"<mount point>/var is not a directory\")\n}\n\nfunc (s *singleServiceSuite) Test_InterceptMountRelative() {\n\tif s.IsCI() && runtime.GOOS == \"darwin\" {\n\t\ts.T().Skip(\"Mount tests don't run on darwin due to macFUSE issues\")\n\t}\n\tif runtime.GOOS == \"windows\" {\n\t\ts.T().Skip(\"Windows mount on driver letters. Relative mounts are not possible\")\n\t}\n\trequire := s.Require()\n\n\tctx := s.Context()\n\tport, cancel := itest.StartLocalHttpEchoServer(ctx, s.ServiceName())\n\tdefer cancel()\n\n\tnwd, err := os.MkdirTemp(\"\", \"mount-\") // Don't use the testing.Tempdir() because deletion is delayed.\n\trequire.NoError(err)\n\tctx = itest.WithWorkingDir(ctx, nwd)\n\tstdout := itest.TelepresenceOk(ctx,\n\t\t\"intercept\", s.ServiceName(), \"--mount\", \"rel-dir\", \"--port\", strconv.Itoa(port))\n\tdefer func() {\n\t\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n\t}()\n\ts.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(s.ServiceName()+`\\s*: intercepted`).MatchString(stdout)\n\t}, 10*time.Second, time.Second)\n\n\ttime.Sleep(200 * time.Millisecond) // List is really fast now, so give the mount some time to become effective\n\tmountPoint := filepath.Join(nwd, \"rel-dir\")\n\tst, err := os.Stat(mountPoint)\n\trequire.NoError(err, \"Stat on <mount point> failed\")\n\trequire.True(st.IsDir(), \"Mount point is not a directory\")\n\tst, err = os.Stat(filepath.Join(mountPoint, \"var\"))\n\trequire.NoError(err, \"Stat on <mount point>/var failed\")\n\trequire.True(st.IsDir(), \"<mount point>/var is not a directory\")\n}\n\nfunc (s *singleServiceSuite) Test_InterceptDetailedOutput() {\n\tif s.IsCI() && runtime.GOOS == \"darwin\" {\n\t\ts.T().Skip(\"Mount tests don't run on darwin due to macFUSE issues\")\n\t}\n\tctx := s.Context()\n\tport, cancel := itest.StartLocalHttpEchoServer(ctx, s.ServiceName())\n\tdefer cancel()\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\",\n\t\t\"--port\", strconv.Itoa(port),\n\t\t\"--detailed-output\",\n\t\t\"--output\", \"json\",\n\t\ts.ServiceName())\n\tdefer func() {\n\t\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n\t}()\n\tvar iInfo intercept.Info\n\trequire := s.Require()\n\trequire.NoError(json.Unmarshal([]byte(stdout), &iInfo))\n\ts.Equal(iInfo.Name, s.ServiceName())\n\ts.Equal(iInfo.Disposition, \"ACTIVE\")\n\ts.Equal(iInfo.WorkloadKind, \"Deployment\")\n\ts.Equal(iInfo.TargetPort, int32(port))\n\ts.Equal(iInfo.Environment[\"TELEPRESENCE_CONTAINER\"], \"echo-server\")\n\tm := iInfo.Mount\n\trequire.NotNil(m)\n\ts.NotNil(net.ParseIP(m.PodIP))\n\ts.NotZero(m.Port)\n\ts.Equal(agentconfig.ExportsMountPoint+\"/echo-server\", m.RemoteDir)\n\trequire.Len(m.Mounts, 1)\n\ts.Contains(m.Mounts, \"/var/run/secrets/kubernetes.io\")\n}\n\nfunc (s *singleServiceSuite) Test_NoInterceptorResponse() {\n\tif s.IsCI() && runtime.GOOS == \"darwin\" {\n\t\ts.T().Skip(\"Mount tests don't run on darwin due to macFUSE issues\")\n\t}\n\tif runtime.GOOS == \"windows\" {\n\t\ts.T().Skip(\"Windows mount on driver letters. Relative mounts are not possible\")\n\t}\n\ttime.Sleep(2000 * time.Millisecond) // List is really fast now, so give the mount some time to become effective\n\trequire := s.Require()\n\n\tctx := s.Context()\n\n\tnwd, err := os.MkdirTemp(\"\", \"mount-\") // Don't use the testing.Tempdir() because deletion is delayed.\n\trequire.NoError(err)\n\tctx = itest.WithWorkingDir(ctx, nwd)\n\tstdout := itest.TelepresenceOk(ctx,\n\t\t\"intercept\", s.ServiceName(), \"--mount\", \"rel-dir\", \"--port\", \"8443\")\n\tdefer func() {\n\t\titest.TelepresenceOk(ctx, \"leave\", s.ServiceName())\n\t}()\n\ts.Contains(stdout, \"Using Deployment \"+s.ServiceName())\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(s.ServiceName()+`\\s*: intercepted`).MatchString(stdout)\n\t}, 10*time.Second, time.Second)\n\n\ttime.Sleep(2000 * time.Millisecond) // List is really fast now, so give the mount some time to become effective\n\ts.CapturePodLogs(ctx, s.ServiceName(), \"traffic-agent\", s.AppNamespace())\n\n\tmountPoint := filepath.Join(nwd, \"rel-dir\")\n\tst, err := os.Stat(mountPoint)\n\trequire.NoError(err, \"Stat on <mount point> failed\")\n\trequire.True(st.IsDir(), \"Mount point is not a directory\")\n\tst, err = os.Stat(filepath.Join(mountPoint, \"var\"))\n\trequire.NoError(err, \"Stat on <mount point>/var failed\")\n\trequire.True(st.IsDir(), \"<mount point>/var is not a directory\")\n\n\t// Bombard the echo service with lots of traffic. It's intercepted and will redirect the\n\t// traffic to the interceptor, but there's no such process listening. This must not\n\t// result in stream congestion that kills the intercept.\n\turl := \"http://\" + s.ServiceName()\n\tfor i := 0; i < 1000; i++ {\n\t\tgo func() {\n\t\t\thc := http.Client{Timeout: 100 * time.Millisecond}\n\t\t\tresp, err := hc.Get(url)\n\t\t\tif err == nil {\n\t\t\t\tresp.Body.Close()\n\t\t\t}\n\t\t}()\n\t}\n\n\t// Verify that we still have a functional mount\n\tst, err = os.Stat(mountPoint)\n\trequire.NoError(err, \"Stat on <mount point> failed\")\n\trequire.True(st.IsDir(), \"Mount point is not a directory\")\n\tst, err = os.Stat(filepath.Join(mountPoint, \"var\"))\n\trequire.NoError(err, \"Stat on <mount point>/var failed\")\n\trequire.True(st.IsDir(), \"<mount point>/var is not a directory\")\n}\n"
  },
  {
    "path": "integration_test/itest/apply_app.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\nfunc ApplyEchoService(ctx context.Context, name, namespace string, port int) {\n\tApplyService(ctx, name, namespace, \"ghcr.io/telepresenceio/echo-server:0.3.1\", port, 8080)\n}\n\nfunc ApplyService(ctx context.Context, name, namespace, image string, port, targetPort int) {\n\tt := getT(ctx)\n\tt.Helper()\n\trequire.NoError(t, Kubectl(ctx, namespace, \"create\", \"deploy\", name, \"--image\", image), \"failed to create deployment %s\", name)\n\trequire.NoError(t, Kubectl(ctx, namespace, \"expose\", \"deploy\", name, \"--port\", strconv.Itoa(port), \"--target-port\", strconv.Itoa(targetPort)),\n\t\t\"failed to expose deployment %s\", name)\n\trequire.NoError(t, Kubectl(ctx, namespace, \"rollout\", \"status\", \"-w\", \"deployment/\"+name), \"failed to deploy %s\", name)\n}\n\nfunc DeleteSvcAndWorkload(ctx context.Context, workload, name, namespace string) {\n\tassert.NoError(getT(ctx), Kubectl(ctx, namespace, \"delete\", \"--ignore-not-found\", \"--grace-period\", \"3\", \"svc,\"+workload, name),\n\t\t\"failed to delete service and %s %s\", workload, name)\n}\n\n// ApplyApp calls kubectl apply -n <namespace> -f on the given app + .yaml found in testdata/k8s relative\n// to the directory returned by GetWorkingDir.\nfunc ApplyApp(ctx context.Context, name, namespace, workload string) {\n\tt := getT(ctx)\n\tt.Helper()\n\tmanifest := filepath.Join(\"testdata\", \"k8s\", name+\".yaml\")\n\trequire.NoError(t, Kubectl(ctx, namespace, \"apply\", \"-f\", manifest), \"failed to apply %s\", manifest)\n\trequire.NoError(t, RolloutStatusWait(ctx, namespace, workload))\n}\n\n// DeleteApp calls kubectl delete -n <namespace> -f on the given app + .yaml found in testdata/k8s relative\n// to the directory returned by GetWorkingDir.\nfunc DeleteApp(ctx context.Context, name, namespace string) {\n\tt := getT(ctx)\n\tt.Helper()\n\tmanifest := filepath.Join(\"testdata\", \"k8s\", name+\".yaml\")\n\trequire.NoError(t, Kubectl(ctx, namespace, \"delete\", \"-f\", manifest), \"failed to delete %s\", manifest)\n}\n\ntype AppPort struct {\n\tServicePortName   string\n\tServicePortNumber uint16\n\tTargetPortName    string\n\tTargetPortNumber  uint16\n\tProtocol          string\n\tAppProtocol       string\n}\ntype AppData struct {\n\tServiceName    string\n\tDeploymentName string\n\tAppName        string\n\tContainerName  string\n\tImage          string\n\tPullPolicy     string\n\tPorts          []AppPort\n\tEnv            map[string]string\n}\n\n// ApplyAppTemplate calls kubectl apply -n <namespace> -f on the given app + .yaml found in testdata/k8s relative\n// to the directory returned by GetWorkingDir.\nfunc ApplyAppTemplate(ctx context.Context, namespace string, app *AppData) {\n\tt := getT(ctx)\n\tt.Helper()\n\tr, err := OpenTemplate(ctx, filepath.Join(GetOSSRoot(ctx), \"testdata\", \"k8s\", \"svc-deploy.goyaml\"), app)\n\trequire.NoError(t, err)\n\trequire.NoError(t, Kubectl(dos.WithStdin(ctx, r), namespace, \"apply\", \"-f\", \"-\"), \"failed to apply template\")\n\twl := app.DeploymentName\n\tif wl == \"\" {\n\t\twl = app.AppName\n\t}\n\trequire.NoError(t, RolloutStatusWait(ctx, namespace, \"deploy/\"+wl))\n}\n\nfunc RolloutStatusWait(ctx context.Context, namespace, workload string) error {\n\tctx, cancel := context.WithTimeout(ctx, PodCreateTimeout(ctx))\n\tdefer cancel()\n\tswitch {\n\tcase strings.HasPrefix(workload, \"pod/\"):\n\t\treturn Kubectl(ctx, namespace, \"wait\", workload, \"--for\", \"condition=ready\")\n\tcase strings.HasPrefix(workload, \"rollout/\"):\n\t\treturn Kubectl(ctx, namespace, \"argo\", \"rollouts\", \"status\", strings.TrimPrefix(workload, \"rollout/\"))\n\tcase strings.HasPrefix(workload, \"replicaset/\"), strings.HasPrefix(workload, \"statefulset/\"):\n\t\tfor {\n\t\t\tstatus := struct {\n\t\t\t\tReadyReplicas int `json:\"readyReplicas,omitempty\"`\n\t\t\t\tReplicas      int `json:\"replicas,omitempty\"`\n\t\t\t}{}\n\t\t\tstdout, err := KubectlOut(ctx, namespace, \"get\", workload, \"-o\", \"jsonpath={..status}\")\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err = json.Unmarshal([]byte(stdout), &status); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif status.ReadyReplicas == status.Replicas {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\ttime.Sleep(3 * time.Second)\n\t\t}\n\t}\n\treturn Kubectl(ctx, namespace, \"rollout\", \"status\", \"-w\", workload)\n}\n"
  },
  {
    "path": "integration_test/itest/assertions.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype Requirements struct {\n\t*require.Assertions\n}\n\nfunc (r *Requirements) EventuallyContext(ctx context.Context, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...any) {\n\tr.Eventually(func() bool {\n\t\tif ctx.Err() != nil {\n\t\t\treturn true\n\t\t}\n\t\treturn condition()\n\t}, waitFor, tick, msgAndArgs...)\n\tr.NoError(ctx.Err())\n}\n\ntype Assertions struct {\n\t*assert.Assertions\n}\n\nfunc (r *Assertions) EventuallyContext(ctx context.Context, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...any) bool {\n\treturn r.Eventually(func() bool {\n\t\tif ctx.Err() != nil {\n\t\t\treturn true\n\t\t}\n\t\treturn condition()\n\t}, waitFor, tick, msgAndArgs...) || r.NoError(ctx.Err())\n}\n"
  },
  {
    "path": "integration_test/itest/cluster.go",
    "content": "package itest\n\nimport (\n\t\"archive/zip\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\t\"unicode/utf8\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\tcore \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\tk8sruntime \"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/slice\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nconst (\n\tpurposeLabel       = \"tp-cli-testing\"\n\tAssignPurposeLabel = \"purpose=\" + purposeLabel\n\tTestUser           = \"telepresence-test-developer\"\n)\n\ntype Cluster interface {\n\tCapturePodLogs(ctx context.Context, app, container, ns string) string\n\tExecutable() (string, error)\n\tGeneralError() error\n\tGlobalEnv(context.Context) dos.MapEnv\n\tInitialize(context.Context) context.Context\n\tIsCI() bool\n\tIsIPv6() bool\n\tLargeFileTestDisabled() bool\n\tSetGeneralError(error)\n\tSuffix() string\n\tUninstallTrafficManager(ctx context.Context, managerNamespace string, args ...string)\n\tPackageHelmChart(ctx context.Context) (string, error)\n\tGetValuesForHelm(ctx context.Context, values map[string]any, release bool) []string\n\tGetSetArgsForHelm(ctx context.Context, values map[string]any, release bool) []string\n\tGetK8SCluster(ctx context.Context, context, managerNamespace string) (*k8s.Cluster, error)\n\tTelepresenceHelmInstallOK(ctx context.Context, upgrade bool, args ...string) string\n\tTelepresenceHelmInstall(ctx context.Context, upgrade bool, args ...string) (string, error)\n\tUserdPProf() uint16\n\tRootdPProf() uint16\n\n\tAgentImage() string\n\tAgentVersion() semver.Version\n\tAgentRegistry() string\n\n\tClientImage() string\n\tClientRegistry() string\n\tClientVersion() semver.Version\n\n\t// ClientIsVersion returns true if the final version of the ClientVersion is included\n\t// in the given version range.\n\tClientIsVersion(versionRange string) bool\n\n\tManagerImage() string\n\tManagerRegistry() string\n\tManagerVersion() semver.Version\n\n\t// ManagerIsVersion returns true if the final version of the ManagerVersion is included\n\t// in the given version range.\n\tManagerIsVersion(versionRange string) bool\n\n\t// UseRancherLocalPath returns true if the rancher-local-path provisioner is used.\n\t// See: https://github.com/rancher/local-path-provisioner\n\tUseLocalPathProvisioner() bool\n}\n\n// The cluster is created once and then reused by all tests. It ensures that:\n//\n//   - executable and the images are built once\n//   - a docker repository is available\n//   - built images are pushed to the docker repository\n//   - a cluster is available\ntype cluster struct {\n\tsuffix                string\n\tisCI                  bool\n\tipv6                  bool\n\texecutable            string\n\tkubeConfig            string\n\tgeneralError          error\n\tlogCapturingPods      sync.Map\n\tuserdPProf            uint16\n\trootdPProf            uint16\n\tself                  Cluster\n\tlargeFileTestDisabled bool\n\n\tagentImage   string\n\tclientImage  string\n\tmanagerImage string\n\n\tagentRegistry   string\n\tclientRegistry  string\n\tmanagerRegistry string\n\n\tagentVersion         semver.Version\n\tclientVersion        semver.Version\n\tmanagerVersion       semver.Version\n\tlocalPathProvisioner bool\n}\n\n//nolint:gochecknoglobals // extension point\nvar ExtendClusterFunc = func(c Cluster) Cluster {\n\treturn c\n}\n\nfunc WithCluster(ctx context.Context, f func(ctx context.Context)) {\n\ts := cluster{}\n\ts.self = &s\n\tec := ExtendClusterFunc(&s)\n\tctx = withGlobalHarness(ctx, ec)\n\tctx = ec.Initialize(ctx)\n\tdefer s.tearDown(ctx)\n\tt := getT(ctx)\n\tif !t.Failed() {\n\t\tf(s.withBasicConfig(ctx, t))\n\t}\n}\n\nfunc (s *cluster) SetSelf(self Cluster) {\n\ts.self = self\n}\n\nfunc (s *cluster) Initialize(ctx context.Context) context.Context {\n\ts.suffix, s.isCI = dos.LookupEnv(ctx, \"GITHUB_SHA\")\n\tif s.isCI {\n\t\t// Use 7 characters of SHA to avoid busting k8s 60 character name limit\n\t\tif len(s.suffix) > 7 {\n\t\t\ts.suffix = s.suffix[:7]\n\t\t}\n\t} else {\n\t\ts.suffix = strconv.Itoa(os.Getpid())\n\t}\n\tt := getT(ctx)\n\n\tv := dos.Getenv(ctx, \"TELEPRESENCE_VERSION\")\n\trequire.NotEmpty(t, v, \"TELEPRESENCE_VERSION must be set\")\n\tvar err error\n\tversion.Structured, err = semver.Parse(strings.TrimPrefix(v, \"v\"))\n\trequire.NoError(t, err)\n\tversion.Version = v\n\n\tif v = dos.Getenv(ctx, \"DEV_CLIENT_VERSION\"); v != \"\" {\n\t\ts.clientVersion, err = semver.Parse(v)\n\t\trequire.NoError(t, err)\n\t} else {\n\t\ts.clientVersion = version.Structured\n\t}\n\n\tif v = dos.Getenv(ctx, \"DEV_MANAGER_VERSION\"); v != \"\" {\n\t\ts.managerVersion, err = semver.Parse(v)\n\t\trequire.NoError(t, err)\n\t} else {\n\t\ts.managerVersion = version.Structured\n\t}\n\n\tif v = dos.Getenv(ctx, \"DEV_AGENT_VERSION\"); v != \"\" {\n\t\ts.agentVersion, err = semver.Parse(v)\n\t\trequire.NoError(t, err)\n\t} else {\n\t\ts.agentVersion = s.managerVersion\n\t}\n\n\t// We cannot use t.TempDir() here, because it will not mount correctly in\n\t// rancher-desktop and docker-desktop (unless they are configured to allow\n\t// mounts directly from /tmp). So we use a tempdir in BUILD_OUTPUT instead\n\t// because it's believed to be both mountable and writable.\n\ttempDir := dos.Getenv(ctx, \"TELEPRESENCE_TEMP_DIR\")\n\tif tempDir == \"\" {\n\t\ttempDir = filepath.Join(BuildOutput(ctx), \"tmp\")\n\t}\n\t_ = dos.RemoveAll(ctx, tempDir)\n\trequire.NoError(t, dos.MkdirAll(ctx, tempDir, 0o777))\n\tctx = withTempDirBase(ctx, &tempDirBase{tempDir: tempDir})\n\tt.Cleanup(func() {\n\t\t_ = os.RemoveAll(tempDir)\n\t})\n\n\tregistry := dos.Getenv(ctx, \"TELEPRESENCE_REGISTRY\")\n\tif registry == \"\" {\n\t\tregistry = \"ghcr.io/telepresenceio\"\n\t}\n\n\ts.clientRegistry = dos.Getenv(ctx, \"DEV_CLIENT_REGISTRY\")\n\tif s.clientRegistry == \"\" {\n\t\ts.clientRegistry = registry\n\t}\n\ts.managerRegistry = dos.Getenv(ctx, \"DEV_MANAGER_REGISTRY\")\n\tif s.managerRegistry == \"\" {\n\t\ts.managerRegistry = registry\n\t}\n\ts.agentRegistry = dos.Getenv(ctx, \"DEV_AGENT_REGISTRY\")\n\tif s.agentRegistry == \"\" {\n\t\ts.agentRegistry = s.managerRegistry\n\t}\n\n\ts.kubeConfig = dos.Getenv(ctx, \"DEV_KUBECONFIG\")\n\tif s.kubeConfig == \"\" {\n\t\tlr := clientcmd.NewDefaultClientConfigLoadingRules()\n\t\trequire.True(t, len(lr.Precedence) > 0, \"Unable to figure out KUBECONFIG\")\n\t\ts.kubeConfig = lr.Precedence[0]\n\t}\n\n\ts.clientImage = dos.Getenv(ctx, \"DEV_CLIENT_IMAGE\")\n\tif s.clientImage == \"\" {\n\t\ts.clientImage = \"telepresence\"\n\t}\n\tctx = WithClientImage(ctx, &Image{\n\t\tName:     s.clientImage,\n\t\tTag:      s.clientVersion.String(),\n\t\tRegistry: s.clientRegistry,\n\t})\n\ts.managerImage = dos.Getenv(ctx, \"DEV_MANAGER_IMAGE\")\n\tif s.managerImage == \"\" {\n\t\ts.managerImage = \"tel2\"\n\t}\n\tctx = WithImage(ctx, &Image{\n\t\tName:     s.managerImage,\n\t\tTag:      s.managerVersion.String(),\n\t\tRegistry: s.managerRegistry,\n\t})\n\ts.agentImage = dos.Getenv(ctx, \"DEV_AGENT_IMAGE\")\n\tif s.agentImage == \"\" {\n\t\ts.agentImage = s.managerImage\n\t}\n\tctx = WithAgentImage(ctx, &Image{\n\t\tName:     s.agentImage,\n\t\tTag:      s.agentVersion.String(),\n\t\tRegistry: s.agentRegistry,\n\t})\n\n\tif pp := dos.Getenv(ctx, \"DEV_USERD_PROFILING_PORT\"); pp != \"\" {\n\t\tport, err := strconv.ParseUint(pp, 10, 16)\n\t\trequire.NoError(t, err)\n\t\ts.userdPProf = uint16(port)\n\t}\n\tif pp := dos.Getenv(ctx, \"DEV_ROOTD_PROFILING_PORT\"); pp != \"\" {\n\t\tport, err := strconv.ParseUint(pp, 10, 16)\n\t\trequire.NoError(t, err)\n\t\ts.rootdPProf = uint16(port)\n\t}\n\tif pp := dos.Getenv(ctx, \"DEV_LOCAL_PATH_PROVISIONER\"); pp != \"\" {\n\t\ts.localPathProvisioner, _ = strconv.ParseBool(pp)\n\t}\n\n\texe := \"telepresence\"\n\tif runtime.GOOS == \"windows\" {\n\t\texe = \"telepresence.exe\"\n\t}\n\ts.executable = filepath.Join(BuildOutput(ctx), \"bin\", exe)\n\n\tvar executable string\n\tif !s.clientVersion.EQ(version.Structured) {\n\t\texecutable = s.downloadBinary(ctx, t, s.clientVersion)\n\t} else {\n\t\texecutable = s.executable\n\t}\n\tclog.Infof(ctx, \"Using binary %s\", executable)\n\tctx = WithExecutable(ctx, executable)\n\n\tif ipv6, err := strconv.ParseBool(dos.Getenv(ctx, \"DEV_IPV6_CLUSTER\")); err == nil {\n\t\ts.ipv6 = ipv6\n\t} else {\n\t\toutput, err := Output(ctx, \"kubectl\", \"--namespace\", \"kube-system\", \"get\", \"svc\", \"kube-dns\", \"-o\", \"jsonpath={.spec.clusterIP}\")\n\t\tif err == nil {\n\t\t\tip, err := netip.ParseAddr(strings.TrimSpace(output))\n\t\t\tassert.NoError(t, err)\n\t\t\tif ip.Is6() {\n\t\t\t\tclog.Info(ctx, \"Using IPv6 because the kube-dns.kube-system has an IPv6 IP\")\n\t\t\t\ts.ipv6 = true\n\t\t\t}\n\t\t}\n\t}\n\n\ts.ensureQuit(ctx)\n\ts.ensureNoManager(ctx)\n\t_ = Run(ctx, \"kubectl\", \"delete\", \"-f\", filepath.Join(\"testdata\", \"k8s\", \"client_rbac.yaml\"))\n\terr = Run(ctx, \"kubectl\", \"delete\", \"ns,svc,deploy,clusterrole,clusterrolebinding,mutatingwebhookconfiguration,pvc,pv\", \"-l\", AssignPurposeLabel)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"kubectl delete: %v\", err)\n\t}\n\treturn ctx\n}\n\nfunc (s *cluster) downloadBinary(ctx context.Context, t testing.TB, v semver.Version) string {\n\tpath := filepath.Join(BuildOutput(ctx), \"downloads\")\n\terr := os.MkdirAll(path, 0o755)\n\trequire.NoError(t, err)\n\tcdPath := filepath.Join(path, \"telepresence-\"+v.String())\n\tif _, err := os.Stat(cdPath); err == nil {\n\t\treturn cdPath\n\t}\n\n\tcdURL := \"https://github.com/telepresenceio/telepresence/releases/download/v%s/telepresence-%s-%s\"\n\tcdURL = fmt.Sprintf(cdURL, v, runtime.GOOS, runtime.GOARCH)\n\tif runtime.GOOS == \"windows\" {\n\t\tcdURL += \".zip\"\n\t\tcdPath += \".zip\"\n\t}\n\tclog.Infof(ctx, \"Downloading telepresence binary from %s\", cdURL)\n\tcdFile, err := os.Create(cdPath)\n\trequire.NoError(t, err)\n\trsp, err := http.Get(cdURL)\n\trequire.NoError(t, err)\n\tbdy := rsp.Body\n\t_, err = io.Copy(cdFile, bdy)\n\tbdy.Close()\n\trequire.NoError(t, err)\n\tif runtime.GOOS == \"windows\" {\n\t\trequire.NoError(t, cdFile.Close())\n\t\tunzip(t, cdFile.Name(), cdPath)\n\t\treturn filepath.Join(cdPath, \"telepresence.exe\")\n\t} else {\n\t\trequire.NoError(t, cdFile.Chmod(0o755))\n\t\trequire.NoError(t, cdFile.Close())\n\t\treturn cdFile.Name()\n\t}\n}\n\nfunc unzip(t testing.TB, zipFile, dir string) {\n\tuz, err := zip.OpenReader(zipFile)\n\trequire.NoError(t, err)\n\tdefer uz.Close()\n\tfor _, f := range uz.File {\n\t\trc, err := f.Open()\n\t\trequire.NoError(t, err)\n\t\tout, err := os.OpenFile(filepath.Join(dir, f.Name), os.O_CREATE|os.O_WRONLY, f.FileInfo().Mode())\n\t\trequire.NoError(t, err)\n\t\t_, err = io.Copy(out, rc)\n\t\trc.Close()\n\t\tout.Close()\n\t\trequire.NoError(t, err)\n\t}\n}\n\nfunc (s *cluster) tearDown(ctx context.Context) {\n\ts.ensureQuit(ctx)\n\tif s.kubeConfig != \"\" {\n\t\tctx = WithWorkingDir(ctx, GetOSSRoot(ctx))\n\t\t_ = Run(ctx, \"kubectl\", \"delete\", \"-f\", filepath.Join(\"testdata\", \"k8s\", \"client_rbac.yaml\"))\n\t\t_ = Run(ctx, \"kubectl\", \"delete\", \"--wait=false\", \"all\", \"-l\", AssignPurposeLabel)\n\t}\n}\n\nfunc (s *cluster) ensureQuit(ctx context.Context) {\n\t// Ensure that no telepresence is running when the tests start\n\t_, _, _ = Telepresence(ctx, \"quit\", \"-s\") //nolint:dogsled // don't care about any of the returns\n}\n\nfunc (s *cluster) ensureNoManager(ctx context.Context) {\n\tout, err := Output(ctx, \"helm\", \"list\", \"-A\", \"--output\", \"json\")\n\tt := getT(ctx)\n\trequire.NoError(t, err)\n\tvar es []map[string]any\n\terr = json.Unmarshal([]byte(out), &es)\n\trequire.NoError(t, err)\n\tfor {\n\t\tix := slices.IndexFunc(es, func(v map[string]any) bool {\n\t\t\treturn v[\"name\"] == \"traffic-manager\"\n\t\t})\n\t\tif ix < 0 {\n\t\t\tbreak\n\t\t}\n\t\te := es[ix]\n\t\tes = slices.Delete(es, ix, ix+1)\n\t\tns := e[\"namespace\"].(string)\n\t\tif regexp.MustCompile(`^ambassador-[0-9a-f]+(-[0-9])?$`).MatchString(ns) {\n\t\t\ts.UninstallTrafficManager(ctx, ns)\n\t\t} else {\n\t\t\tt.Fatalf(\"%s is already installed in namespace %s. Please uninstall before testing.\", e[\"chart\"], ns)\n\t\t}\n\t}\n}\n\n// PodCreateTimeout will return a timeout suitable for operations that create pods.\n// This is longer when running against clusters that scale up nodes on demand for new pods.\nfunc PodCreateTimeout(c context.Context) time.Duration {\n\tswitch GetProfile(c) {\n\tcase GkeAutopilotProfile:\n\t\treturn 5 * time.Minute\n\tcase DefaultProfile:\n\t\tfallthrough\n\tdefault: // this really shouldn't be happening but hey\n\t\treturn 180 * time.Second\n\t}\n}\n\nfunc (s *cluster) withBasicConfig(c context.Context, t *testing.T) context.Context {\n\tconfig := client.GetDefaultConfig()\n\tlogLevels := config.LogLevels()\n\tlogLevels.CLI = slog.LevelDebug\n\tlogLevels.UserDaemon = slog.LevelDebug\n\tlogLevels.RootDaemon = slog.LevelDebug\n\tlogLevels.KubeAuthDaemon = slog.LevelDebug\n\n\tto := config.Timeouts()\n\tto.PrivateClusterConnect = 60 * time.Second\n\tto.PrivateEndpointDial = 10 * time.Second\n\tto.PrivateHelm = PodCreateTimeout(c)\n\tto.PrivateIntercept = 30 * time.Second\n\tto.PrivateProxyDial = 30 * time.Second\n\tto.PrivateRoundtripLatency = 5 * time.Second\n\tto.PrivateTrafficManagerAPI = 45 * time.Second\n\tto.PrivateTrafficManagerConnect = 30 * time.Second\n\tto.PrivateConnectivityCheck = 0\n\n\tif s.ManagerVersion().EQ(version.Structured) {\n\t\timages := config.Images()\n\t\timages.PrivateRegistry = s.self.ManagerRegistry()\n\t\tif agentImage := GetAgentImage(c); agentImage != nil {\n\t\t\timages.PrivateAgentImage = agentImage.FQName()\n\t\t\timages.PrivateWebhookRegistry = agentImage.Registry\n\t\t}\n\t\tif clientImage := GetClientImage(c); clientImage != nil {\n\t\t\timages.PrivateClientImage = clientImage.FQName()\n\t\t}\n\t}\n\n\tconfig.Grpc().MaxReceiveSizeV, _ = resource.ParseQuantity(\"10Mi\")\n\tconfig.Intercept().UseFtp = true\n\tif s.ClientIsVersion(\">=2.23.0\") {\n\t\tconfig.Intercept().MountsRoot = TempDir(c)\n\t}\n\tconfig = config.Merge(client.GetConfig(c))\n\n\tconfigYaml, err := config.MarshalYAML()\n\trequire.NoError(t, err)\n\tconfigYamlStr := string(configYaml)\n\n\tconfigDir := TempDir(c)\n\tc = filelocation.WithAppUserConfigDir(c, configDir)\n\tc, err = SetConfig(c, configDir, configYamlStr)\n\trequire.NoError(t, err)\n\treturn c\n}\n\nfunc (s *cluster) GlobalEnv(ctx context.Context) dos.MapEnv {\n\tglobalEnv := dos.MapEnv{\n\t\t\"KUBECONFIG\": s.kubeConfig,\n\t}\n\tyes := struct{}{}\n\tincludeEnv := map[string]struct{}{\n\t\t\"HOME\":                      yes,\n\t\t\"PATH\":                      yes,\n\t\t\"LOGNAME\":                   yes,\n\t\t\"USER\":                      yes,\n\t\t\"TMPDIR\":                    yes,\n\t\t\"MAKELEVEL\":                 yes,\n\t\t\"TELEPRESENCE_MAX_LOGFILES\": yes,\n\t}\n\tif runtime.GOOS == \"windows\" {\n\t\tincludeEnv[\"APPDATA\"] = yes\n\t\tincludeEnv[\"AppData\"] = yes\n\t\tincludeEnv[\"LOCALAPPDATA\"] = yes\n\t\tincludeEnv[\"LocalAppData\"] = yes\n\t\tincludeEnv[\"OS\"] = yes\n\t\tincludeEnv[\"TEMP\"] = yes\n\t\tincludeEnv[\"TMP\"] = yes\n\t\tincludeEnv[\"Path\"] = yes\n\t\tincludeEnv[\"PATHEXT\"] = yes\n\t\tincludeEnv[\"ProgramFiles\"] = yes\n\t\tincludeEnv[\"ProgramData\"] = yes\n\t\tincludeEnv[\"SystemDrive\"] = yes\n\t\tincludeEnv[\"USERPROFILE\"] = yes\n\t\tincludeEnv[\"USERNAME\"] = yes\n\t\tincludeEnv[\"windir\"] = yes\n\t}\n\tfor _, env := range dos.Environ(ctx) {\n\t\tif eqIdx := strings.IndexByte(env, '='); eqIdx > 0 {\n\t\t\tkey := env[:eqIdx]\n\t\t\tif _, ok := includeEnv[key]; ok {\n\t\t\t\tglobalEnv[key] = env[eqIdx+1:]\n\t\t\t}\n\t\t}\n\t}\n\treturn globalEnv\n}\n\nfunc (s *cluster) Executable() (string, error) {\n\treturn s.executable, nil\n}\n\nfunc (s *cluster) GeneralError() error {\n\treturn s.generalError\n}\n\nfunc (s *cluster) IsCI() bool {\n\treturn s.isCI\n}\n\nfunc (s *cluster) IsIPv6() bool {\n\treturn s.ipv6\n}\n\nfunc (s *cluster) LargeFileTestDisabled() bool {\n\treturn s.largeFileTestDisabled\n}\n\nfunc (s *cluster) AgentImage() string {\n\treturn s.agentImage\n}\n\nfunc (s *cluster) AgentRegistry() string {\n\treturn s.agentRegistry\n}\n\nfunc (s *cluster) AgentVersion() semver.Version {\n\treturn s.agentVersion\n}\n\nfunc (s *cluster) ClientImage() string {\n\treturn s.clientImage\n}\n\nfunc (s *cluster) ClientRegistry() string {\n\treturn s.clientRegistry\n}\n\nfunc (s *cluster) ClientVersion() semver.Version {\n\treturn s.clientVersion\n}\n\nfunc isFinalIncluded(vr string, v semver.Version) bool {\n\treturn semver.MustParseRange(vr)(semver.MustParse(v.FinalizeVersion()))\n}\n\n// ClientIsVersion returns true if the final version of the ClientVersion is included\n// in the given version range.\nfunc (s *cluster) ClientIsVersion(vr string) bool {\n\treturn isFinalIncluded(vr, s.ClientVersion())\n}\n\nfunc (s *cluster) ManagerImage() string {\n\treturn s.managerImage\n}\n\nfunc (s *cluster) ManagerRegistry() string {\n\treturn s.managerRegistry\n}\n\nfunc (s *cluster) ManagerVersion() semver.Version {\n\treturn s.managerVersion\n}\n\n// ManagerIsVersion returns true if the final version of the ManagerVersion is included\n// in the given version range.\nfunc (s *cluster) ManagerIsVersion(vr string) bool {\n\treturn isFinalIncluded(vr, s.ManagerVersion())\n}\n\nfunc (s *cluster) SetGeneralError(err error) {\n\ts.generalError = err\n}\n\nfunc (s *cluster) Suffix() string {\n\treturn s.suffix\n}\n\nfunc (s *cluster) UserdPProf() uint16 {\n\treturn s.userdPProf\n}\n\nfunc (s *cluster) RootdPProf() uint16 {\n\treturn s.rootdPProf\n}\n\nfunc (s *cluster) UseLocalPathProvisioner() bool {\n\treturn s.localPathProvisioner\n}\n\nfunc (s *cluster) CapturePodLogs(ctx context.Context, app, container, ns string) string {\n\tvar pods []string\n\tfor i := 0; ; i++ {\n\t\trunningPods := RunningPodNames(ctx, app, ns)\n\t\tif len(runningPods) > 0 {\n\t\t\tif container == \"\" {\n\t\t\t\tpods = runningPods\n\t\t\t} else {\n\t\t\t\tfor _, pod := range runningPods {\n\t\t\t\t\tcns, err := KubectlOut(ctx, ns, \"get\", \"pods\", pod, \"-o\", \"jsonpath={.spec.containers[*].name}\")\n\t\t\t\t\tif err == nil && slice.Contains(strings.Split(cns, \" \"), container) {\n\t\t\t\t\t\tpods = append(pods, pod)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif len(pods) > 0 || i == 5 {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(2 * time.Second)\n\t}\n\n\tif len(pods) == 0 {\n\t\tif container == \"\" {\n\t\t\tclog.Errorf(ctx, \"found no %s pods in namespace %s\", app, ns)\n\t\t} else {\n\t\t\tclog.Errorf(ctx, \"found no %s pods in namespace %s with a %s container\", app, ns, container)\n\t\t}\n\t\treturn \"\"\n\t}\n\tpresent := struct{}{}\n\n\tvar pod string\n\tfor i, key := range pods {\n\t\tif container != \"\" {\n\t\t\tkey += \"/\" + container\n\t\t}\n\t\tif _, ok := s.logCapturingPods.LoadOrStore(key, present); !ok {\n\t\t\tpod = pods[i]\n\t\t\tbreak\n\t\t}\n\t}\n\tif pod == \"\" {\n\t\treturn \"\" // All pods already captured\n\t}\n\n\t// Use another logger to avoid errors due to logs arriving after the tests complete.\n\tctx = clog.WithLogger(ctx, slog.Default())\n\tlogName := pod\n\tif container != \"\" {\n\t\tlogName = fmt.Sprintf(\"%s-%s\", pod, container)\n\t}\n\tlogFile, err := os.Create(\n\t\tfilepath.Join(filelocation.AppUserLogDir(ctx), fmt.Sprintf(\"%s-%s.log\", time.Now().Format(\"20060102T150405\"), logName)))\n\tif err != nil {\n\t\ts.logCapturingPods.Delete(pod)\n\t\tclog.Errorf(ctx, \"unable to create pod logfile %s: %v\", logFile.Name(), err)\n\t\treturn \"\"\n\t}\n\n\targs := []string{\"--namespace\", ns, \"logs\", \"-f\", pod}\n\tif container != \"\" {\n\t\targs = append(args, \"-c\", container)\n\t}\n\t// Let command die when the pod that it logs die\n\tcmd := Command(context.WithoutCancel(ctx), \"kubectl\", args...)\n\tcmd.Stdout = logFile\n\tcmd.Stderr = logFile\n\tready := make(chan string, 1)\n\treadyClosed := false\n\tgo func() {\n\t\tdefer func() {\n\t\t\t_ = logFile.Close()\n\t\t\ts.logCapturingPods.Delete(pod)\n\t\t}()\n\t\terr := cmd.Start()\n\t\tif err == nil {\n\t\t\tif container == \"\" {\n\t\t\t\tclog.Infof(ctx, \"Capturing logs for pod %s\", pod)\n\t\t\t} else {\n\t\t\t\tclog.Infof(ctx, \"Capturing logs for pod %s, container %s\", pod, container)\n\t\t\t}\n\t\t\tready <- logFile.Name()\n\t\t\tclose(ready)\n\t\t\treadyClosed = true\n\t\t\terr = cmd.Wait()\n\t\t}\n\t\tif err != nil {\n\t\t\tif container == \"\" {\n\t\t\t\tclog.Errorf(ctx, \"log capture for pod %s failed: %v\", pod, err)\n\t\t\t} else {\n\t\t\t\tclog.Errorf(ctx, \"log capture for pod %s, container %s failed: %v\", pod, container, err)\n\t\t\t}\n\t\t\tif !readyClosed {\n\t\t\t\tclose(ready)\n\t\t\t}\n\t\t}\n\t}()\n\tselect {\n\tcase <-ctx.Done():\n\t\tclog.Infof(ctx, \"log capture for pod %s interrupted prior to start\", pod)\n\t\treturn \"\"\n\tcase file := <-ready:\n\t\treturn file\n\t}\n}\n\nfunc (s *cluster) GetK8SCluster(ctx context.Context, context, managerNamespace string) (*k8s.Cluster, error) {\n\t_ = os.Setenv(\"KUBECONFIG\", KubeConfig(ctx))\n\tflags := map[string]string{\n\t\t\"namespace\": managerNamespace,\n\t}\n\tif context != \"\" {\n\t\tflags[\"context\"] = context\n\t}\n\tcfgAndFlags, err := k8s.NewKubeconfig(ctx, false, flags, managerNamespace, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn k8s.NewCluster(cfgAndFlags, nil)\n}\n\nfunc KubeConfig(ctx context.Context) string {\n\tkubeConf, _ := LookupEnv(ctx, \"KUBECONFIG\")\n\treturn kubeConf\n}\n\nconst sensitivePrefix = \"--$sensitive$--\"\n\n// WrapSensitive wraps an argument sent to Command so that it doesn't get logged verbatim. This can\n// be used for commands like \"telepresence login --apikey NNNN\" where the NNN shouldn't be visible\n// in the logs. If NNN Is wrapped using this function, it will appear as \"***\" in the logs.\nfunc WrapSensitive(s string) string {\n\treturn sensitivePrefix + s\n}\n\n// Command creates and returns an exec.Cmd  initialized with the global environment\n// from the cluster harness and any other environment that has been added using the\n// WithEnv() function.\nfunc Command(ctx context.Context, executable string, args ...string) *exec.Cmd {\n\tgetT(ctx).Helper()\n\t// Ensure that command has a timestamp and is somewhat readable\n\tdbgArgs := args\n\tcopied := false\n\tfor i, a := range args {\n\t\tif strings.HasPrefix(a, sensitivePrefix) {\n\t\t\tif !copied {\n\t\t\t\tdbgArgs = make([]string, len(args))\n\t\t\t\tcopy(dbgArgs, args)\n\t\t\t\targs = make([]string, len(args))\n\t\t\t\tcopy(args, dbgArgs)\n\t\t\t\tcopied = true\n\t\t\t}\n\t\t\tdbgArgs[i] = \"***\"\n\t\t\targs[i] = strings.TrimPrefix(a, sensitivePrefix)\n\t\t}\n\t}\n\tclog.Debugf(ctx, \"executing %s\", shellquote.ShellString(filepath.Base(executable), dbgArgs))\n\tcmd := proc.CommandContext(ctx, executable, args...)\n\tcmd.Env = EnvironMap(ctx).Environ()\n\tcmd.Dir = GetWorkingDir(ctx)\n\tcmd.Stdin = dos.Stdin(ctx)\n\treturn cmd\n}\n\nfunc EnvironMap(ctx context.Context) dos.MapEnv {\n\tenv := GetGlobalHarness(ctx).GlobalEnv(ctx)\n\tmaps.Merge(env, getEnv(ctx))\n\treturn env\n}\n\n// TelepresenceOk executes the CLI command in a new process and requires the result to be OK.\nfunc TelepresenceOk(ctx context.Context, args ...string) string {\n\tt := getT(ctx)\n\tt.Helper()\n\tstdout, stderr, err := Telepresence(ctx, args...)\n\tif !assert.NoError(t, err) {\n\t\tt.Fatalf(\"telepresence was unable to run, stdout %s\", stdout)\n\t}\n\trequire.NoError(t, err, \"telepresence was unable to run, stdout %s\", stdout)\n\tif (strings.HasPrefix(stderr, \"Warning:\") || strings.Contains(stderr, \"has been deprecated\")) && !strings.ContainsRune(stderr, '\\n') {\n\t\t// Accept warnings, but log them.\n\t\tclog.Warn(ctx, stderr)\n\t} else {\n\t\tassert.Empty(t, stderr, \"Expected stderr to be empty, but got: %s\", stderr)\n\t}\n\treturn stdout\n}\n\n// Telepresence executes the CLI command in a new process.\nfunc Telepresence(ctx context.Context, args ...string) (string, string, error) {\n\tt := getT(ctx)\n\tt.Helper()\n\tcmd := TelepresenceCmd(ctx, args...)\n\tstdout := cmd.Stdout.(*strings.Builder)\n\tstderr := cmd.Stderr.(*strings.Builder)\n\terr := cmd.Run()\n\terrStr := strings.TrimSpace(stderr.String())\n\tif err != nil {\n\t\terr = RunError(err, []byte(errStr))\n\t}\n\treturn strings.TrimSpace(stdout.String()), errStr, err\n}\n\n// TelepresenceCmd creates an exec.Cmd using the Command function. Before the command is created,\n// the environment is extended with DEV_TELEPRESENCE_CONFIG_DIR from filelocation.AppUserConfigDir\n// and DEV_TELEPRESENCE_LOG_DIR from filelocation.AppUserLogDir.\nfunc TelepresenceCmd(ctx context.Context, args ...string) *exec.Cmd {\n\tt := getT(ctx)\n\tt.Helper()\n\n\tvar stdout, stderr strings.Builder\n\tctx = WithEnv(ctx, map[string]string{\n\t\t\"DEV_TELEPRESENCE_CONFIG_DIR\": filelocation.AppUserConfigDir(ctx),\n\t\t\"DEV_TELEPRESENCE_LOG_DIR\":    filelocation.AppUserLogDir(ctx),\n\t\t\"TELEPRESENCE_PROGRESS\":       \"plain\",\n\t})\n\n\tgh := GetGlobalHarness(ctx)\n\tif len(args) > 0 && (args[0] == \"connect\") {\n\t\trest := args[1:]\n\t\targs = append(make([]string, 0, len(args)+3), args[0])\n\t\tif user := GetUser(ctx); user != \"default\" {\n\t\t\targs = append(args, \"--as\", \"system:serviceaccount:\"+user)\n\t\t}\n\t\tif gh.UserdPProf() > 0 {\n\t\t\targs = append(args, \"--userd-profiling-port\", strconv.Itoa(int(gh.UserdPProf())))\n\t\t}\n\t\tif gh.RootdPProf() > 0 {\n\t\t\targs = append(args, \"--rootd-profiling-port\", strconv.Itoa(int(gh.RootdPProf())))\n\t\t}\n\t\targs = append(args, rest...)\n\t}\n\tcmd := Command(ctx, GetExecutable(ctx), args...)\n\tcmd.Stdout = &stdout\n\tcmd.Stderr = &stderr\n\treturn cmd\n}\n\n// TelepresenceDisconnectOk tells telepresence to quit and asserts that the stdout contains the correct output.\nfunc TelepresenceDisconnectOk(ctx context.Context, args ...string) {\n\tAssertDisconnectOutput(ctx, TelepresenceOk(ctx, append([]string{\"quit\"}, args...)...))\n}\n\n// TelepresenceDisconnect tells telepresence to quit and asserts that the stdout contains the correct output.\nfunc TelepresenceDisconnect(ctx context.Context, args ...string) {\n\t_, _, _ = Telepresence(ctx, append([]string{\"quit\"}, args...)...) //nolint:nolintlint,dogsled\n}\n\n// AssertDisconnectOutput asserts that the stdout contains the correct output from a telepresence quit command.\nfunc AssertDisconnectOutput(ctx context.Context, stdout string) {\n\tt := getT(ctx)\n\tassert.True(t, strings.Contains(stdout, \"Disconnected\") || strings.Contains(stdout, \"Not connected\"))\n\tif t.Failed() {\n\t\tt.Logf(\"Disconnect output was %q\", stdout)\n\t}\n}\n\n// TelepresenceQuitOk tells telepresence to quit and asserts that the stdout contains the correct output.\nfunc TelepresenceQuitOk(ctx context.Context, args ...string) {\n\tAssertQuitOutput(ctx, TelepresenceOk(ctx, append([]string{\"quit\", \"-s\"}, args...)...))\n}\n\n// TelepresenceQuit tells telepresence to quit but disregards any errors. Suitable for use in a defer statement\n// or when tearing down a suite.\nfunc TelepresenceQuit(ctx context.Context, args ...string) {\n\t_, _, _ = Telepresence(ctx, append([]string{\"quit\", \"-s\"}, args...)...) //nolint:nolintlint,dogsled\n}\n\n// AssertQuitOutput asserts that the stdout contains the correct output from a telepresence quit command.\nfunc AssertQuitOutput(ctx context.Context, stdout string) {\n\tfor _, ex := range []string{\"\", \"Quit\", \"Telepresence Daemons quitting...done\", \"Telepresence Daemons have already quit\"} {\n\t\tif strings.Contains(stdout, ex) {\n\t\t\treturn\n\t\t}\n\t}\n\tt := getT(ctx)\n\tt.Fail()\n\tt.Logf(\"Quit output was %q\", stdout)\n}\n\n// RunError checks if the given err is a *exit.ExitError, and if so, extracts\n// Stderr and the ExitCode from it.\nfunc RunError(err error, out []byte) error {\n\tvar ee *exec.ExitError\n\tif errors.As(err, &ee) {\n\t\tswitch {\n\t\tcase len(ee.Stderr) > 0:\n\t\t\terr = fmt.Errorf(\"%s, exit code %d\", string(ee.Stderr), ee.ExitCode())\n\t\tcase utf8.ValidString(string(out)):\n\t\t\terr = fmt.Errorf(\"%s, exit code %d\", string(out), ee.ExitCode())\n\t\tdefault:\n\t\t\terr = fmt.Errorf(\"exit code %d\", ee.ExitCode())\n\t\t}\n\t}\n\treturn err\n}\n\n// Run runs the given command and arguments and returns an error if the command failed.\nfunc Run(ctx context.Context, exe string, args ...string) error {\n\tgetT(ctx).Helper()\n\tout, err := Command(ctx, exe, args...).CombinedOutput()\n\tif err != nil {\n\t\treturn RunError(err, out)\n\t}\n\treturn nil\n}\n\n// Output runs the given command and arguments and returns its combined output and an error if the command failed.\nfunc Output(ctx context.Context, exe string, args ...string) (string, error) {\n\tgetT(ctx).Helper()\n\tcmd := Command(ctx, exe, args...)\n\tstderr := bytes.Buffer{}\n\tcmd.Stderr = &stderr\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn string(out), RunError(err, stderr.Bytes())\n\t}\n\treturn string(out), nil\n}\n\n// Kubectl runs kubectl with the default context and the given namespace, or in the default namespace if the given\n// namespace is an empty string.\nfunc Kubectl(ctx context.Context, namespace string, args ...string) error {\n\treturn retryKubectl(ctx, namespace, args, func(args []string) error { return Run(ctx, \"kubectl\", args...) })\n}\n\n// KubectlOut runs kubectl with the default context and the application namespace and returns its combined output.\nfunc KubectlOut(ctx context.Context, namespace string, args ...string) (out string, err error) {\n\terr = retryKubectl(ctx, namespace, args, func(args []string) error {\n\t\tout, err = Output(ctx, \"kubectl\", args...)\n\t\treturn err\n\t})\n\treturn out, err\n}\n\nfunc retryKubectl(ctx context.Context, namespace string, args []string, f func([]string) error) error {\n\tgetT(ctx).Helper()\n\tvar ks []string\n\tif namespace != \"\" {\n\t\t// Add --namespace <namespace> before first flag argument\n\t\tks = make([]string, 0, len(args)+2)\n\t\tpos := -1\n\t\tfor i, arg := range args {\n\t\t\tif strings.HasPrefix(arg, \"-\") {\n\t\t\t\tpos = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tks = append(ks, arg)\n\t\t}\n\t\tks = append(ks, \"--namespace\", namespace)\n\t\tif pos >= 0 {\n\t\t\tks = append(ks, args[pos:]...)\n\t\t}\n\t\targs = ks\n\t}\n\tb := backoff.NewExponentialBackOff(\n\t\tbackoff.WithInitialInterval(2*time.Second),\n\t\tbackoff.WithMaxInterval(7*time.Second),\n\t\tbackoff.WithMaxElapsedTime(30*time.Second),\n\t)\n\treturn backoff.Retry(func() error {\n\t\terr := f(args)\n\t\tif err != nil && !strings.Contains(err.Error(), \"(ServiceUnavailable)\") {\n\t\t\terr = backoff.Permanent(err)\n\t\t}\n\t\treturn err\n\t}, backoff.WithContext(b, ctx))\n}\n\nfunc CreateNamespaces(ctx context.Context, namespaces ...string) {\n\tt := getT(ctx)\n\tt.Helper()\n\twg := sync.WaitGroup{}\n\twg.Add(len(namespaces))\n\tfor _, ns := range namespaces {\n\t\tgo func(ns string) {\n\t\t\tdefer wg.Done()\n\t\t\tassert.NoError(t, Kubectl(ctx, \"\", \"create\", \"namespace\", ns), \"failed to create namespace %q\", ns)\n\t\t\tassert.NoError(t, Kubectl(ctx, \"\", \"label\", \"namespace\", ns, AssignPurposeLabel, fmt.Sprintf(\"app.kubernetes.io/name=%s\", ns)))\n\t\t}(ns)\n\t}\n\twg.Wait()\n}\n\nfunc DeleteNamespaces(ctx context.Context, namespaces ...string) {\n\tt := getT(ctx)\n\tt.Helper()\n\twg := sync.WaitGroup{}\n\twg.Add(len(namespaces))\n\tfor _, ns := range namespaces {\n\t\tif t.Failed() {\n\t\t\tif out, err := KubectlOut(ctx, ns, \"get\", \"events\", \"--field-selector\", \"type!=Normal\"); err == nil {\n\t\t\t\tclog.Debugf(ctx, \"Events where type != Normal from namespace %s\\n%s\", ns, out)\n\t\t\t}\n\t\t}\n\t\tgo func(ns string) {\n\t\t\tdefer wg.Done()\n\t\t\tassert.NoError(t, Kubectl(ctx, \"\", \"delete\", \"namespace\", \"--wait=false\", ns))\n\t\t}(ns)\n\t}\n\twg.Wait()\n}\n\n// StartLocalHttpEchoServerWithAddr is like StartLocalHttpEchoServer but binds to a specific host instead of localhost.\nfunc StartLocalHttpEchoServerWithAddr(ctx context.Context, name, addr string, responseFunc func(string, *http.Request) string) (int, context.CancelFunc) {\n\tctx, cancel := context.WithCancel(ctx)\n\tlc := net.ListenConfig{}\n\tl, err := lc.Listen(ctx, \"tcp\", addr)\n\trequire.NoError(getT(ctx), err, \"failed to listen on localhost\")\n\tif responseFunc == nil {\n\t\tresponseFunc = func(name string, r *http.Request) string {\n\t\t\treturn fmt.Sprintf(\"%s from intercept at %s\", name, r.URL.Path)\n\t\t}\n\t}\n\tsc := &http.Server{\n\t\tHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tioutil.Print(w, responseFunc(name, r))\n\t\t}),\n\t}\n\tgo func() {\n\t\t_ = sc.Serve(l)\n\t}()\n\tgo func() {\n\t\t<-ctx.Done()\n\t\tctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second)\n\t\tdefer cancel()\n\t\terr = sc.Shutdown(ctx)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"http server on %s exited with error: %v\", addr, err)\n\t\t} else {\n\t\t\tclog.Errorf(ctx, \"http server on %s exited\", addr)\n\t\t}\n\t}()\n\treturn l.Addr().(*net.TCPAddr).Port, cancel\n}\n\n// StartLocalHttpEchoServer starts a local http server that echoes a line with the given name and\n// the current URL path. The port is returned together with a function that cancels the server.\nfunc StartLocalHttpEchoServer(ctx context.Context, name string) (int, context.CancelFunc) {\n\treturn StartLocalHttpEchoServerWithAddr(ctx, name, \"localhost:0\", nil)\n}\n\n// PingInterceptedEchoServer assumes that a server has been created using StartLocalHttpEchoServer and\n// that an intercept is active for the given svc and svcPort that will redirect to that local server.\nfunc PingInterceptedEchoServer(ctx context.Context, svc, svcPort string, headers ...string) {\n\twl := svc\n\tif slashIdx := strings.IndexByte(svc, '/'); slashIdx > 0 {\n\t\twl = svc[slashIdx+1:]\n\t\tsvc = svc[:slashIdx]\n\t}\n\texpectedOutput := fmt.Sprintf(\"%s from intercept at /\", wl)\n\tPingInterceptedEchoServerAndExpect(ctx, svc, svcPort, expectedOutput, headers...)\n}\n\n// PingInterceptedEchoServerAndExpect assumes that a server has been created using StartLocalHttpEchoServer and\n// that an intercept is active for the given svc and svcPort that will redirect to that local server, and the server\n// will echo the given expectedOutput.\nfunc PingInterceptedEchoServerAndExpect(ctx context.Context, svc, svcPort, expectedOutput string, headers ...string) {\n\tclog.Infof(ctx, \"pinging %s, expecting output: %s\", net.JoinHostPort(svc, svcPort), expectedOutput)\n\n\tping := func() bool {\n\t\t// condition\n\t\tips, err := net.DefaultResolver.LookupIP(ctx, \"ip\", svc)\n\t\tif err != nil {\n\t\t\tclog.Info(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tips = iputil.UniqueSorted(ips)\n\t\tif len(ips) != 1 {\n\t\t\tclog.Infof(ctx, \"Lookup for %s returned %v\", svc, ips)\n\t\t\treturn false\n\t\t}\n\n\t\thc := http.Client{Timeout: 2 * time.Second}\n\t\trq, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf(\"http://%s\", net.JoinHostPort(ips[0].String(), svcPort)), nil)\n\t\tif err != nil {\n\t\t\tclog.Info(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tfor _, h := range headers {\n\t\t\tkv := strings.SplitN(h, \"=\", 2)\n\t\t\trq.Header[kv[0]] = []string{kv[1]}\n\t\t}\n\t\tresp, err := hc.Do(rq)\n\t\tif err != nil {\n\t\t\tclog.Info(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tdefer resp.Body.Close()\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\tclog.Info(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tr := string(body)\n\t\tif r != expectedOutput {\n\t\t\tclog.Infof(ctx, \"body: %q != %q\", r, expectedOutput)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}\n\tif ping() {\n\t\treturn\n\t}\n\trequire.Eventually(getT(ctx), ping, time.Minute, 5*time.Second, `body of %q equals %q`, \"http://\"+svc, expectedOutput)\n}\n\nfunc WithConfig(c context.Context, modifierFunc func(config client.Config)) context.Context {\n\t// Quit a running daemon. We're changing the directory where its config resides.\n\tTelepresenceQuitOk(c)\n\n\tt := getT(c)\n\tcfgVal := reflect.ValueOf(client.GetConfig(c)).Elem()\n\tcfgCopyVal := reflect.New(cfgVal.Type())\n\tcfgCopyVal.Elem().Set(cfgVal) // By value copy\n\tconfigCopy := cfgCopyVal.Interface()\n\tmodifierFunc(configCopy.(client.Config))\n\tconfigYaml, err := configCopy.(client.Config).MarshalYAML()\n\trequire.NoError(t, err)\n\tconfigYamlStr := string(configYaml)\n\tconfigDir, err := os.MkdirTemp(TempDir(c), \"config\")\n\trequire.NoError(t, err)\n\tc, err = SetConfig(c, configDir, configYamlStr)\n\trequire.NoError(t, err)\n\treturn c\n}\n\nfunc WithKubeConfigExtension(ctx context.Context, extProducer func(*api.Cluster) map[string]any) context.Context {\n\tkc := KubeConfig(ctx)\n\tt := getT(ctx)\n\tcfg, err := clientcmd.LoadFromFile(kc)\n\trequire.NoError(t, err, \"unable to read %s\", kc)\n\tcc := cfg.Contexts[cfg.CurrentContext]\n\trequire.NotNil(t, cc, \"unable to get current context from config\")\n\tcluster := cfg.Clusters[cc.Cluster]\n\trequire.NotNil(t, cluster, \"unable to get current cluster from config\")\n\n\tem := cluster.Extensions\n\tif em == nil {\n\t\tem = map[string]k8sruntime.Object{}\n\t}\n\traw, err := json.Marshal(extProducer(cluster))\n\trequire.NoError(t, err, \"unable to json.Marshal extension map\")\n\tem[\"telepresence.io\"] = &k8sruntime.Unknown{Raw: raw}\n\tcluster.Extensions = em\n\n\tkctx := *cc\n\tkctx.Cluster = \"extra\"\n\tcfg = &api.Config{\n\t\tKind:           \"Config\",\n\t\tAPIVersion:     \"v1\",\n\t\tPreferences:    api.Preferences{},\n\t\tClusters:       map[string]*api.Cluster{\"extra\": cluster},\n\t\tContexts:       map[string]*api.Context{\"extra\": &kctx},\n\t\tCurrentContext: \"extra\",\n\t}\n\tkubeconfigFileName := filepath.Join(t.TempDir(), \"kubeconfig\")\n\trequire.NoError(t, clientcmd.WriteToFile(*cfg, kubeconfigFileName), \"unable to write modified kubeconfig\")\n\treturn WithEnv(ctx, map[string]string{\"KUBECONFIG\": strings.Join([]string{kc, kubeconfigFileName}, string([]byte{os.PathListSeparator}))})\n}\n\nfunc WithKubeConfig(ctx context.Context, cfg *api.Config) context.Context {\n\tkubeconfigFileName := filepath.Join(TempDir(ctx), \"kubeconfig\")\n\trequire.NoError(getT(ctx), clientcmd.WriteToFile(*cfg, kubeconfigFileName), \"unable to write modified kubeconfig\")\n\treturn WithEnv(ctx, map[string]string{\"KUBECONFIG\": kubeconfigFileName})\n}\n\nfunc RunningPods(ctx context.Context, svc, ns string) []core.Pod {\n\treturn RunningPodsSelector(ctx, ns, labels.SelectorFromSet(map[string]string{\"app\": svc}))\n}\n\nfunc RunningPodsSelector(ctx context.Context, ns string, selector labels.Selector) []core.Pod {\n\tout, err := KubectlOut(ctx, ns, \"get\", \"pods\", \"-o\", \"json\", \"--field-selector\", \"status.phase==Running\", \"-l\", selector.String())\n\tif err != nil {\n\t\tgetT(ctx).Log(err.Error())\n\t\treturn nil\n\t}\n\tvar pm core.PodList\n\tif err := json.UnmarshalRead(strings.NewReader(out), &pm); err != nil {\n\t\tgetT(ctx).Log(err.Error())\n\t\treturn nil\n\t}\n\treturn slices.DeleteFunc(pm.Items, func(pod core.Pod) bool {\n\t\tfor _, cn := range pod.Status.ContainerStatuses {\n\t\t\tif r := cn.State.Running; r != nil && !r.StartedAt.IsZero() {\n\t\t\t\t// At least one container is still running.\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n}\n\n// RunningPodNames return the names of running pods with app=<service name>. Running here means\n// that at least one container is still running. I.e. the pod might well be terminating\n// but still considered running.\nfunc RunningPodNames(ctx context.Context, svc, ns string) []string {\n\tpods := RunningPods(ctx, svc, ns)\n\tpodNames := make([]string, len(pods))\n\tfor i := range pods {\n\t\tpodNames[i] = pods[i].Name\n\t}\n\tclog.Infof(ctx, \"Running pods %v\", podNames)\n\treturn podNames\n}\n\n// RunningPodsWithAgents returns the names of running pods with a matching appPrefix that\n// has a running traffic-agent container.\nfunc RunningPodsWithAgents(ctx context.Context, appPrefix, ns string) []string {\n\tout, err := KubectlOut(ctx, ns, \"get\", \"pods\", \"-o\", \"json\", \"--field-selector\", \"status.phase==Running\")\n\tif err != nil {\n\t\tgetT(ctx).Log(err.Error())\n\t\treturn nil\n\t}\n\tvar pm core.PodList\n\tif err := json.UnmarshalRead(strings.NewReader(out), &pm); err != nil {\n\t\tgetT(ctx).Log(err.Error())\n\t\treturn nil\n\t}\n\tpods := make([]string, 0, len(pm.Items))\nnextPod:\n\tfor _, pod := range pm.Items {\n\t\tif !strings.HasPrefix(pod.Labels[\"app\"], appPrefix) {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, cn := range pod.Status.ContainerStatuses {\n\t\t\tif cn.Name == agentconfig.ContainerName && cn.Ready {\n\t\t\t\tif r := cn.State.Running; r != nil && !r.StartedAt.IsZero() {\n\t\t\t\t\tpods = append(pods, pod.Name)\n\t\t\t\t\tcontinue nextPod\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn pods\n}\n"
  },
  {
    "path": "integration_test/itest/config.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\n// SetConfig creates a config from the configYml provided and assigns it to a new context which\n// is returned. Use this if you are testing components of the config.yml, otherwise you can use setDefaultConfig.\nfunc SetConfig(ctx context.Context, configDir, configYml string) (context.Context, error) {\n\tconfig, err := os.Create(filepath.Join(configDir, \"config.yml\"))\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\n\t_, err = config.WriteString(configYml)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tconfig.Close()\n\n\t// Load env if it isn't loaded already\n\tctx = filelocation.WithAppUserConfigDir(ctx, configDir)\n\tif client.GetEnv(ctx) == nil {\n\t\tenv, err := client.LoadEnv()\n\t\tif err != nil {\n\t\t\treturn ctx, err\n\t\t}\n\t\tctx = client.WithEnv(ctx, &env)\n\t}\n\n\tcfg, err := client.LoadConfig(ctx)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tctx = client.WithConfig(ctx, cfg)\n\treturn ctx, nil\n}\n"
  },
  {
    "path": "integration_test/itest/connected.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype connected struct {\n\tTrafficManager\n}\n\nfunc WithConnected(np TrafficManager, f func(ctx context.Context, ch TrafficManager)) {\n\tnp.HarnessT().Run(\"Test_Connected\", func(t *testing.T) {\n\t\tctx := WithT(np.HarnessContext(), t)\n\t\trequire.NoError(t, np.GeneralError())\n\t\tch := &connected{TrafficManager: np}\n\t\tch.PushHarness(ctx, ch.setup, ch.tearDown)\n\t\tdefer ch.PopHarness()\n\t\tf(ctx, ch)\n\t})\n}\n\nfunc (ch *connected) setup(ctx context.Context) bool {\n\tt := getT(ctx)\n\t// Connect using telepresence-test-developer user\n\tstdout, _, err := Telepresence(ctx, \"connect\", \"--namespace\", ch.AppNamespace(), \"--manager-namespace\", ch.ManagerNamespace())\n\tassert.NoError(t, err)\n\tassert.Contains(t, stdout, \"Connected to context\")\n\tif t.Failed() {\n\t\treturn false\n\t}\n\t_, _, err = Telepresence(ctx, \"loglevel\", \"-d30m\", \"debug\")\n\tassert.NoError(t, err)\n\treturn !t.Failed()\n}\n\nfunc (ch *connected) tearDown(ctx context.Context) {\n\tTelepresenceQuitOk(ctx)\n}\n"
  },
  {
    "path": "integration_test/itest/context.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/handler\"\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n)\n\ntype profileKey struct{}\n\ntype Profile string\n\nconst (\n\tDefaultProfile      Profile = \"default\"\n\tGkeAutopilotProfile Profile = \"gke-autopilot\"\n)\n\nfunc withProfile(ctx context.Context) context.Context {\n\tprofile, ok := dos.LookupEnv(ctx, \"TELEPRESENCE_TEST_PROFILE\")\n\tif !ok {\n\t\treturn context.WithValue(ctx, profileKey{}, DefaultProfile)\n\t}\n\tswitch profile {\n\tcase string(DefaultProfile):\n\tcase string(GkeAutopilotProfile):\n\tdefault:\n\t\tpanic(\"Invalid profile \" + profile)\n\t}\n\treturn context.WithValue(ctx, profileKey{}, Profile(profile))\n}\n\nfunc GetProfile(ctx context.Context) Profile {\n\tif profile, ok := ctx.Value(profileKey{}).(Profile); ok {\n\t\treturn profile\n\t}\n\treturn DefaultProfile\n}\n\ntype tContextKey struct{}\n\nfunc TestContext(t *testing.T, ossRoot, moduleRoot string) context.Context {\n\tctx := testutil.NewContext(t, false)\n\tctx = client.WithEnv(ctx, &client.Env{})\n\tctx = SetOSSRoot(ctx, ossRoot)\n\tctx = SetModuleRoot(ctx, moduleRoot)\n\tctx = withProfile(ctx)\n\treturn WithT(ctx, t)\n}\n\nfunc WithT(ctx context.Context, t *testing.T) context.Context {\n\tctx = context.WithValue(ctx, tContextKey{}, t)\n\tctx, cancel := context.WithCancel(\n\t\tclog.WithLogger(ctx,\n\t\t\tslog.New(handler.NewText(handler.Output(t.Output()), handler.EnabledLevel(slog.LevelDebug)))))\n\tt.Cleanup(cancel)\n\treturn ctx\n}\n\nfunc getT(ctx context.Context) *testing.T {\n\tif t, ok := ctx.Value(tContextKey{}).(*testing.T); ok {\n\t\treturn t\n\t}\n\tpanic(\"No *testing.T in context\")\n}\n\ntype ossRootKey struct{}\n\nfunc GetOSSRoot(ctx context.Context) string {\n\tif dir, ok := ctx.Value(ossRootKey{}).(string); ok {\n\t\treturn dir\n\t}\n\tossRoot, err := os.Getwd()\n\tif err != nil {\n\t\tpanic(\"failed to get current directory\")\n\t}\n\treturn ossRoot\n}\n\n// SetOSSRoot sets the OSS module root for the given context to dir.\nfunc SetOSSRoot(ctx context.Context, dir string) context.Context {\n\treturn context.WithValue(ctx, ossRootKey{}, dir)\n}\n\n// WithOSSRoot set the working directory for the Command function to the\n// OSS module root.\nfunc WithOSSRoot(ctx context.Context) context.Context {\n\treturn WithWorkingDir(ctx, GetOSSRoot(ctx))\n}\n\ntype moduleRootKey struct{}\n\nfunc GetModuleRoot(ctx context.Context) string {\n\tif dir, ok := ctx.Value(moduleRootKey{}).(string); ok {\n\t\treturn dir\n\t}\n\tmoduleRoot, err := os.Getwd()\n\tif err != nil {\n\t\tpanic(\"failed to get current directory\")\n\t}\n\treturn filepath.Dir(moduleRoot)\n}\n\n// SetModuleRoot sets the module root for the given context to dir.\nfunc SetModuleRoot(ctx context.Context, dir string) context.Context {\n\treturn context.WithValue(ctx, moduleRootKey{}, dir)\n}\n\n// WithModuleRoot set the working directory for the Command function to the\n// module root.\nfunc WithModuleRoot(ctx context.Context) context.Context {\n\treturn WithWorkingDir(ctx, GetModuleRoot(ctx))\n}\n\ntype dirContextKey struct{}\n\n// WithWorkingDir determines the working directory for the Command function.\nfunc WithWorkingDir(ctx context.Context, dir string) context.Context {\n\treturn context.WithValue(ctx, dirContextKey{}, dir)\n}\n\nfunc GetWorkingDir(ctx context.Context) string {\n\tif dir, ok := ctx.Value(dirContextKey{}).(string); ok {\n\t\treturn dir\n\t}\n\tdir, err := os.Getwd()\n\trequire.NoError(getT(ctx), err, \"failed to get working directory\")\n\treturn dir\n}\n\ntype envContextKey struct{}\n\n// WithEnv adds environment variables to be used by the Command function.\nfunc WithEnv(ctx context.Context, env dos.MapEnv) context.Context {\n\tif prevEnv := getEnv(ctx); prevEnv != nil {\n\t\tmerged := make(dos.MapEnv, len(prevEnv)+len(env))\n\t\tmaps.Merge(merged, prevEnv)\n\t\tmaps.Merge(merged, env)\n\t\tenv = merged\n\t}\n\tctx = context.WithValue(ctx, envContextKey{}, env)\n\tevx, err := client.LoadEnvWith(env)\n\tif err != nil {\n\t\tgetT(ctx).Fatal(err)\n\t}\n\treturn client.WithEnv(ctx, &evx)\n}\n\n// WithoutEnv prevents environment variables to be used by the Command function.\nfunc WithoutEnv(ctx context.Context, keysToRemove []string) context.Context {\n\tenv := getEnv(ctx)\n\tif env == nil {\n\t\treturn ctx\n\t}\n\tenv = maps.Copy(env)\n\tfor _, key := range keysToRemove {\n\t\tdelete(env, key)\n\t}\n\tctx = context.WithValue(ctx, envContextKey{}, env)\n\tevx, err := client.LoadEnvWith(env)\n\tif err != nil {\n\t\tgetT(ctx).Fatal(err)\n\t}\n\treturn client.WithEnv(ctx, &evx)\n}\n\ntype userContextkey struct{}\n\nfunc WithUser(ctx context.Context, clusterUser string) context.Context {\n\treturn context.WithValue(ctx, userContextkey{}, clusterUser)\n}\n\nfunc GetUser(ctx context.Context) string {\n\tif user, ok := ctx.Value(userContextkey{}).(string); ok {\n\t\treturn user\n\t}\n\treturn \"default\"\n}\n\nfunc LookupEnv(ctx context.Context, key string) (value string, ok bool) {\n\tif value, ok = getEnv(ctx)[key]; !ok {\n\t\tvalue, ok = GetGlobalHarness(ctx).GlobalEnv(ctx)[key]\n\t}\n\treturn value, ok\n}\n\nfunc getEnv(ctx context.Context) dos.MapEnv {\n\tif env, ok := ctx.Value(envContextKey{}).(dos.MapEnv); ok {\n\t\treturn env\n\t}\n\treturn nil\n}\n\ntype globalHarnessContextKey struct{}\n\nfunc withGlobalHarness(ctx context.Context, h Cluster) context.Context {\n\treturn context.WithValue(ctx, globalHarnessContextKey{}, h)\n}\n\nfunc GetGlobalHarness(ctx context.Context) Cluster {\n\tif h, ok := ctx.Value(globalHarnessContextKey{}).(Cluster); ok {\n\t\treturn h\n\t}\n\tpanic(\"No globalHarness in context\")\n}\n\ntype exeContextKey struct{}\n\nfunc WithExecutable(ctx context.Context, exe string) context.Context {\n\treturn context.WithValue(ctx, exeContextKey{}, exe)\n}\n\nfunc GetExecutable(ctx context.Context) string {\n\tif user, ok := ctx.Value(exeContextKey{}).(string); ok {\n\t\treturn user\n\t}\n\tpanic(\"No executable in context\")\n}\n"
  },
  {
    "path": "integration_test/itest/env.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n)\n\ntype itestConfig struct {\n\tEnv    map[string]string `json:\"Env,omitempty\"`\n\tConfig client.Config     `json:\"Config,omitempty\"`\n}\n\nfunc LoadEnvAndConfig(ctx context.Context) context.Context {\n\tcf := filepath.Join(filelocation.AppUserConfigDir(ctx), \"itest.yml\")\n\tdata, err := os.ReadFile(cf)\n\tvar icEnv map[string]string\n\ticConfig := client.GetDefaultConfig()\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\tgetT(ctx).Fatal(cf, err)\n\t\t}\n\t} else {\n\t\tvar ic itestConfig\n\t\tdata, err := yaml.YAMLToJSON(data)\n\t\tif err == nil {\n\t\t\tic.Config = icConfig\n\t\t\tic.Config.LogLevels().UserDaemon = slog.LevelDebug\n\t\t\tic.Config.LogLevels().RootDaemon = slog.LevelDebug\n\t\t\terr = json.Unmarshal(data, &ic, true)\n\t\t}\n\t\tif err != nil {\n\t\t\tgetT(ctx).Fatal(cf, err)\n\t\t\treturn ctx\n\t\t}\n\t\ticEnv = ic.Env\n\t}\n\n\tif icEnv == nil {\n\t\ticEnv = make(map[string]string)\n\t}\n\n\tenv := os.Environ()\n\tdosEnv := make(dos.MapEnv, len(env))\n\tfor _, ep := range env {\n\t\tif ix := strings.IndexByte(ep, '='); ix > 0 {\n\t\t\tdosEnv[ep[:ix]] = ep[ix+1:]\n\t\t}\n\t}\n\n\tmaps.Merge(dosEnv, icEnv)\n\n\t// Ensure that build-output/bin is on the path\n\tbuildBin := filepath.Join(BuildOutput(ctx), \"bin\")\n\tpath, ok := dosEnv[\"PATH\"]\n\tif ok {\n\t\tdosEnv[\"PATH\"] = fmt.Sprintf(\"%s%c%s\", buildBin, os.PathListSeparator, path)\n\t} else {\n\t\tdosEnv[\"PATH\"] = buildBin\n\t}\n\n\tctx = client.WithConfig(ctx, icConfig)\n\treturn dos.WithEnv(ctx, dosEnv)\n}\n\nfunc BuildOutput(ctx context.Context) string {\n\treturn filepath.Join(GetModuleRoot(ctx), \"build-output\")\n}\n"
  },
  {
    "path": "integration_test/itest/harness.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"regexp\"\n\t\"runtime/debug\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/suite\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\ntype Harness interface {\n\tCluster\n\n\tPushHarness(ctx context.Context, setup func(ctx context.Context) bool, tearDown func(ctx context.Context))\n\tRunSuite(TestingSuite)\n\n\tHarnessContext() context.Context\n\tSetupSuite()\n\tHarnessT() *testing.T\n\tPopHarness()\n}\n\ntype upAndDown struct {\n\tsetup     func(ctx context.Context) bool\n\ttearDown  func(ctx context.Context)\n\tsetupWith context.Context\n\twasSetup  bool\n}\n\ntype harness struct {\n\tCluster\n\tctx        context.Context\n\tupAndDowns []upAndDown\n\twasSetup   bool\n}\n\nfunc NewContextHarness(ctx context.Context) Harness {\n\treturn &harness{Cluster: GetGlobalHarness(ctx), ctx: ctx}\n}\n\nfunc (h *harness) PushHarness(ctx context.Context, setup func(ctx context.Context) bool, tearDown func(ctx context.Context)) {\n\th.upAndDowns = append(h.upAndDowns, upAndDown{setup: setup, tearDown: tearDown, setupWith: ctx})\n\th.wasSetup = false\n}\n\nfunc (h *harness) HarnessContext() context.Context {\n\tif l := len(h.upAndDowns) - 1; l >= 0 {\n\t\treturn h.upAndDowns[l].setupWith\n\t}\n\treturn h.ctx\n}\n\nfunc (h *harness) RunSuite(s TestingSuite) {\n\tif suiteEnabled(h.HarnessContext(), s) {\n\t\ts.setContext(s.AmendSuiteContext(h.HarnessContext()))\n\t\th.HarnessT().Run(s.SuiteName(), func(t *testing.T) { suite.Run(t, s) })\n\t}\n}\n\nfunc suiteEnabled(ctx context.Context, s TestingSuite) bool {\n\tsuiteRx := dos.Getenv(ctx, \"TEST_SUITE\")\n\tif suiteRx == \"\" {\n\t\treturn true\n\t}\n\tr, err := regexp.Compile(suiteRx)\n\tif err != nil {\n\t\tgetT(ctx).Fatal(err)\n\t}\n\treturn r.MatchString(s.SuiteName())\n}\n\n// SetupSuite calls all functions that has been added with AddSetup in the order they\n// were added. This only happens once. Repeated calls to this function on the same\n// instance will do nothing.\n//\n// Repeated calls are expected since this function will be called before each\n// test.\nfunc (h *harness) SetupSuite() {\n\tif h.wasSetup {\n\t\treturn\n\t}\n\th.wasSetup = true\n\tif err := h.GeneralError(); err != nil {\n\t\th.HarnessT().Fatal(err) // Immediately fail the test if a general error has been set\n\t}\n\tuds := h.upAndDowns\n\tfor i := range uds {\n\t\tupDown := &uds[i]\n\t\tif setup := upDown.setup; setup != nil {\n\t\t\tupDown.setup = nil // Never setup twice\n\t\t\tif upDown.wasSetup = safeSetUp(upDown.setupWith, setup); !upDown.wasSetup {\n\t\t\t\tgetT(h.ctx).FailNow()\n\t\t\t}\n\t\t}\n\t}\n}\n\n// PopHarness calls the tearDown function that was pushed last and removes it.\nfunc (h *harness) PopHarness() {\n\tlast := len(h.upAndDowns) - 1\n\tif last < 0 {\n\t\treturn\n\t}\n\tupDown := &h.upAndDowns[last]\n\th.upAndDowns = h.upAndDowns[:last]\n\tif upDown.setupWith != nil {\n\t\tif tearDown := upDown.tearDown; tearDown != nil {\n\t\t\tupDown.tearDown = nil // Never tear down twice\n\t\t\tif upDown.wasSetup {\n\t\t\t\tsafeTearDown(upDown.setupWith, tearDown)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc safeSetUp(ctx context.Context, f func(context.Context) bool) bool {\n\tdefer failOnPanic(ctx)\n\treturn f(ctx)\n}\n\nfunc safeTearDown(ctx context.Context, f func(context.Context)) {\n\tdefer failOnPanic(ctx)\n\tf(ctx)\n}\n\nfunc failOnPanic(ctx context.Context) {\n\tif r := recover(); r != nil {\n\t\tt := getT(ctx)\n\t\tt.Errorf(\"test panicked: %v\\n%s\", r, debug.Stack())\n\t\tt.FailNow()\n\t}\n}\n\nfunc (h *harness) HarnessT() *testing.T {\n\treturn getT(h.HarnessContext())\n}\n"
  },
  {
    "path": "integration_test/itest/helm.go",
    "content": "package itest\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\trbac \"k8s.io/api/rbac/v1\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/clog\"\n\ttelcharts \"github.com/telepresenceio/telepresence/v2/charts\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/labels\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nfunc (s *cluster) PackageHelmChart(ctx context.Context) (string, error) {\n\tfilename := filepath.Join(getT(ctx).TempDir(), telcharts.TelepresenceChartName+\"-chart.tgz\")\n\tfh, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY, 0o666)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif err := telcharts.WriteChart(telcharts.DirTypeTelepresence, fh, telcharts.TelepresenceChartName, s.self.ManagerVersion()); err != nil {\n\t\t_ = fh.Close()\n\t\treturn \"\", err\n\t}\n\tif err := fh.Close(); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn filename, nil\n}\n\nfunc (s *cluster) GetSetArgsForHelm(ctx context.Context, values map[string]any, release bool) []string {\n\tsettings := s.GetValuesForHelm(ctx, values, release)\n\targs := make([]string, len(settings)*2)\n\tn := 0\n\tfor _, s := range settings {\n\t\targs[n] = \"--set-json\"\n\t\tn++\n\t\targs[n] = s\n\t\tn++\n\t}\n\treturn args\n}\n\nfunc (s *cluster) GetValuesForHelm(ctx context.Context, values map[string]any, release bool) []string {\n\tnss := GetNamespaces(ctx)\n\tsettings := []string{\n\t\t`logLevel=\"debug\"`,\n\t}\n\n\tif s.ManagerVersion().EQ(version.Structured) {\n\t\treg := s.self.ManagerRegistry()\n\t\tif reg == \"local\" {\n\t\t\tsettings = append(settings, `image.pullPolicy=\"Never\"`)\n\t\t\tsettings = append(settings, `agent.image.pullPolicy=\"Never\"`)\n\t\t} else if strings.HasPrefix(reg, \"localhost:\") {\n\t\t\tsettings = append(settings, `image.pullPolicy=\"Always\"`)\n\t\t\tsettings = append(settings, `agent.image.pullPolicy=\"Always\"`)\n\t\t}\n\t}\n\tif nss != nil && nss.Selector != nil {\n\t\tj, err := json.Marshal(nss.Selector)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"unable to marshal selector '%v': %v\", nss.Selector, err)\n\t\t} else {\n\t\t\tsettings = append(settings, `namespaceSelector=`+string(j))\n\t\t}\n\t}\n\tagentImage := GetAgentImage(ctx)\n\tif agentImage != nil {\n\t\tsettings = append(settings,\n\t\t\tfmt.Sprintf(`agent.image.name=%q`, agentImage.Name), // Prevent attempts to retrieve image from SystemA\n\t\t\tfmt.Sprintf(`agent.image.tag=%q`, agentImage.Tag),\n\t\t\tfmt.Sprintf(`agent.image.registry=%q`, agentImage.Registry))\n\t}\n\tif !release {\n\t\tsettings = append(settings, fmt.Sprintf(`image.registry=%q`, s.self.ManagerRegistry()))\n\t}\n\n\tfor k, v := range values {\n\t\tj, err := json.Marshal(v)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"unable to marshal value %v: %v\", v, err)\n\t\t} else {\n\t\t\tsettings = append(settings, k+\"=\"+string(j))\n\t\t}\n\t}\n\treturn settings\n}\n\nfunc (s *cluster) TelepresenceHelmInstallOK(ctx context.Context, upgrade bool, settings ...string) string {\n\tlogFile, err := s.self.TelepresenceHelmInstall(ctx, upgrade, settings...)\n\trequire.NoError(getT(ctx), err)\n\treturn logFile\n}\n\nfunc (s *cluster) TelepresenceHelmInstall(ctx context.Context, upgrade bool, settings ...string) (string, error) {\n\tnss := GetNamespaces(ctx)\n\tsubjectNames := []string{TestUser}\n\tsubjects := make([]rbac.Subject, len(subjectNames))\n\tfor i, s := range subjectNames {\n\t\tsubjects[i] = rbac.Subject{\n\t\t\tKind:      \"ServiceAccount\",\n\t\t\tName:      s,\n\t\t\tNamespace: nss.Namespace,\n\t\t}\n\t}\n\n\ttype xRbac struct {\n\t\tCreate     bool           `json:\"create\"`\n\t\tNamespaced bool           `json:\"namespaced,omitempty\"`\n\t\tSubjects   []rbac.Subject `json:\"subjects,omitempty\"`\n\t\tNamespaces []string       `json:\"namespaces,omitempty\"`\n\t}\n\ttype xAgent struct {\n\t\tImage *Image `json:\"image,omitempty\"`\n\t}\n\tagentImage := GetAgentImage(ctx)\n\tagent := &xAgent{Image: agentImage}\n\ttype xClient struct {\n\t\tRouting map[string][]string `json:\"routing\"`\n\t}\n\ttype xTimeouts struct {\n\t\tAgentArrival string `json:\"agentArrival,omitempty\"`\n\t}\n\tmanagerRbac := xRbac{\n\t\tCreate: true,\n\t}\n\tclientRbac := xRbac{\n\t\tCreate:   true,\n\t\tSubjects: subjects,\n\t}\n\tvx := struct {\n\t\tLogLevel          string           `json:\"logLevel\"`\n\t\tImage             *Image           `json:\"image,omitempty\"`\n\t\tAgent             *xAgent          `json:\"agent,omitempty\"`\n\t\tClientRbac        *xRbac           `json:\"clientRbac\"`\n\t\tManagerRbac       *xRbac           `json:\"managerRbac\"`\n\t\tClient            xClient          `json:\"client\"`\n\t\tTimeouts          xTimeouts        `json:\"timeouts,omitempty\"`\n\t\tNamespaces        []string         `json:\"namespaces,omitempty\"`\n\t\tNamespaceSelector *labels.Selector `json:\"namespaceSelector,omitempty\"`\n\t}{\n\t\tLogLevel:    \"debug\",\n\t\tAgent:       agent,\n\t\tClientRbac:  &clientRbac,\n\t\tManagerRbac: &managerRbac,\n\t\tClient: xClient{\n\t\t\tRouting: map[string][]string{},\n\t\t},\n\t\tTimeouts: xTimeouts{AgentArrival: \"60s\"},\n\t}\n\tif managedNamespaces := nss.Selector.StaticNames(); len(managedNamespaces) > 0 {\n\t\tif s.ManagerIsVersion(\">2.21.x\") {\n\t\t\tvx.Namespaces = managedNamespaces\n\t\t} else {\n\t\t\tif !slices.Contains(managedNamespaces, nss.Namespace) {\n\t\t\t\tmanagedNamespaces = append(managedNamespaces, nss.Namespace)\n\t\t\t}\n\t\t\tsvcAccArg := \"--serviceaccount=\" + nss.Namespace + \":\" + TestUser\n\n\t\t\tif !s.ManagerIsVersion(\">2.21.x\") {\n\t\t\t\tclientRbac.Namespaced = true\n\t\t\t\tclientRbac.Namespaces = managedNamespaces\n\t\t\t\tmanagerRbac.Namespaced = true\n\t\t\t\tmanagerRbac.Namespaces = managedNamespaces\n\t\t\t\trole := \"tele-update-config\"\n\n\t\t\t\t// Agent is removed by removing its entry in the telepresence-agents configmap\n\t\t\t\tfor _, ns := range managedNamespaces {\n\t\t\t\t\terr := Kubectl(ctx, ns, \"create\", \"role\", role, \"--verb=update\", \"--resource=configmaps\", \"--resource-name=telepresence-agents\")\n\t\t\t\t\tif err != nil && !strings.Contains(err.Error(), \"already exists\") {\n\t\t\t\t\t\treturn \"\", err\n\t\t\t\t\t}\n\t\t\t\t\terr = Kubectl(ctx, ns, \"create\", \"rolebinding\", role, \"--role\", role, svcAccArg)\n\t\t\t\t\tif err != nil && !strings.Contains(err.Error(), \"already exists\") {\n\t\t\t\t\t\treturn \"\", err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif !s.ClientIsVersion(\">2.21.x\") && s.ManagerIsVersion(\">2.21.x\") {\n\t\t\t// Clients older than 2.22.0 need several additional permissions.\n\t\t\trole := \"tele-client\"\n\t\t\tfor _, ns := range managedNamespaces {\n\t\t\t\tr := rbac.Role{\n\t\t\t\t\tTypeMeta: meta.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"rbac.authorization.k8s.io/v1\",\n\t\t\t\t\t\tKind:       \"Role\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: meta.ObjectMeta{\n\t\t\t\t\t\tName:      role,\n\t\t\t\t\t\tNamespace: ns,\n\t\t\t\t\t},\n\t\t\t\t\tRules: []rbac.PolicyRule{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tVerbs:     []string{\"get\", \"list\", \"watch\"},\n\t\t\t\t\t\t\tAPIGroups: []string{\"apps\"},\n\t\t\t\t\t\t\tResources: []string{\"deployments\", \"replicasets\", \"statefulsets\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tVerbs:     []string{\"get\", \"list\", \"watch\"},\n\t\t\t\t\t\t\tAPIGroups: []string{\"argoproj.io\"},\n\t\t\t\t\t\t\tResources: []string{\"rollouts\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tVerbs:     []string{\"get\", \"list\", \"watch\"},\n\t\t\t\t\t\t\tAPIGroups: []string{\"\"},\n\t\t\t\t\t\t\tResources: []string{\"services\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\trj, err := yaml.Marshal(&r)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t\trb := rbac.RoleBinding{\n\t\t\t\t\tTypeMeta: meta.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"rbac.authorization.k8s.io/v1\",\n\t\t\t\t\t\tKind:       \"RoleBinding\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: meta.ObjectMeta{\n\t\t\t\t\t\tName:      role,\n\t\t\t\t\t\tNamespace: ns,\n\t\t\t\t\t},\n\t\t\t\t\tSubjects: subjects,\n\t\t\t\t\tRoleRef: rbac.RoleRef{\n\t\t\t\t\t\tAPIGroup: \"rbac.authorization.k8s.io\",\n\t\t\t\t\t\tKind:     \"Role\",\n\t\t\t\t\t\tName:     role,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\trbj, err := yaml.Marshal(&rb)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t\trj = append(rj, []byte(\"\\n---\\n\")...)\n\t\t\t\trj = append(rj, rbj...)\n\t\t\t\terr = Kubectl(dos.WithStdin(ctx, bytes.NewReader(rj)), ns, \"apply\", \"-f\", \"-\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tvx.NamespaceSelector = nss.Selector\n\t}\n\n\tvx.Image = GetImage(ctx)\n\tif s.ManagerVersion().EQ(s.ClientVersion()) {\n\t\treg := s.ManagerRegistry()\n\t\tif reg == \"local\" {\n\t\t\t// Using Kind/minikube/k3d/Docker Desktop with images loaded directly into the cluster.\n\t\t\t// They are automatically present and must not be pulled.\n\t\t\tvx.Image.PullPolicy = \"Never\"\n\t\t\tvx.Agent.Image.PullPolicy = \"Never\"\n\t\t} else if strings.HasPrefix(reg, \"localhost:\") {\n\t\t\t// Using a local registry. Images must always be pulled to get the latest build.\n\t\t\tvx.Image.PullPolicy = \"Always\"\n\t\t\tvx.Agent.Image.PullPolicy = \"Always\"\n\t\t}\n\t}\n\n\tss, err := yaml.Marshal(&vx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvaluesFile := filepath.Join(getT(ctx).TempDir(), \"values.yaml\")\n\tif err := os.WriteFile(valuesFile, ss, 0o644); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tverb := \"install\"\n\tif upgrade {\n\t\tverb = \"upgrade\"\n\t}\n\targs := []string{\"helm\", verb, \"-n\", nss.Namespace, \"-f\", valuesFile}\n\tif !s.ManagerVersion().EQ(s.ClientVersion()) {\n\t\t// Need to use the built executable because the client version doesn't handle the --version flag.\n\t\tctx = WithExecutable(ctx, s.executable)\n\t\tif !s.ManagerVersion().EQ(version.Structured) {\n\t\t\targs = append(args, \"--version\", s.ManagerVersion().String())\n\t\t}\n\t}\n\targs = append(args, settings...)\n\tctx = WithoutEnv(ctx, []string{\"TELEPRESENCE_REGISTRY\", \"TELEPRESENCE_VERSION\"})\n\n\tif _, _, err = Telepresence(WithUser(ctx, \"default\"), args...); err != nil {\n\t\treturn \"\", err\n\t}\n\tif err = RolloutStatusWait(ctx, nss.Namespace, \"deploy/\"+agentconfig.ManagerAppName); err != nil {\n\t\treturn \"\", err\n\t}\n\tlogFileName := s.self.CapturePodLogs(ctx, agentconfig.ManagerAppName, \"\", nss.Namespace)\n\n\tif !s.ManagerIsVersion(\">2.21.x\") {\n\t\t// Give the manager time to perform rollouts, listen to telepresence-agents configmap, etc.\n\t\ttime.Sleep(2 * time.Second)\n\t}\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn logFileName, nil\n}\n\nfunc (s *cluster) UninstallTrafficManager(ctx context.Context, managerNamespace string, args ...string) {\n\tt := getT(ctx)\n\tctx = WithUser(ctx, \"default\")\n\tTelepresenceOk(ctx, append([]string{\"helm\", \"uninstall\", \"--manager-namespace\", managerNamespace}, args...)...)\n\n\t// Helm uninstall does deletions asynchronously, so let's wait until the deployment is gone\n\tassert.Eventually(t, func() bool { return len(RunningPodNames(ctx, agentconfig.ManagerAppName, managerNamespace)) == 0 },\n\t\t60*time.Second, 4*time.Second, \"traffic-manager deployment was not removed\")\n\tTelepresenceQuitOk(ctx)\n}\n"
  },
  {
    "path": "integration_test/itest/image.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\ntype Image struct {\n\tName       string `json:\"name,omitempty\"`\n\tTag        string `json:\"tag,omitempty\"`\n\tRegistry   string `json:\"registry,omitempty\"`\n\tPullPolicy string `json:\"pullPolicy,omitempty\"`\n}\n\nfunc (img *Image) FQName() string {\n\tnb := strings.Builder{}\n\tif img.Registry != \"\" {\n\t\tnb.WriteString(img.Registry)\n\t\tnb.WriteByte('/')\n\t}\n\tnb.WriteString(img.Name)\n\tif img.Tag != \"\" {\n\t\tnb.WriteByte(':')\n\t\tnb.WriteString(img.Tag)\n\t}\n\treturn nb.String()\n}\n\nfunc ImageFromEnv(ctx context.Context, env, defaultTag, defaultRegistry string) *Image {\n\tif imgQN, ok := dos.LookupEnv(ctx, env); ok {\n\t\timg := new(Image)\n\t\ti := strings.LastIndexByte(imgQN, '/')\n\t\tif i >= 0 {\n\t\t\timg.Registry = imgQN[:i]\n\t\t\timgQN = imgQN[i+1:]\n\t\t} else {\n\t\t\timg.Registry = defaultRegistry\n\t\t}\n\t\tif i = strings.IndexByte(imgQN, ':'); i > 0 {\n\t\t\timg.Name = imgQN[:i]\n\t\t\timg.Tag = imgQN[i+1:]\n\t\t} else {\n\t\t\timg.Name = imgQN\n\t\t\timg.Tag = defaultTag\n\t\t}\n\t\treturn img\n\t}\n\treturn nil\n}\n\ntype imageContextKey struct{}\n\nfunc WithImage(ctx context.Context, image *Image) context.Context {\n\treturn context.WithValue(ctx, imageContextKey{}, image)\n}\n\nfunc GetImage(ctx context.Context) *Image {\n\tif image, ok := ctx.Value(imageContextKey{}).(*Image); ok {\n\t\treturn image\n\t}\n\treturn nil\n}\n\ntype clientImageContextKey struct{}\n\nfunc WithClientImage(ctx context.Context, image *Image) context.Context {\n\treturn context.WithValue(ctx, clientImageContextKey{}, image)\n}\n\nfunc GetClientImage(ctx context.Context) *Image {\n\tif image, ok := ctx.Value(clientImageContextKey{}).(*Image); ok {\n\t\treturn image\n\t}\n\treturn nil\n}\n\ntype agentImageContextKey struct{}\n\nfunc WithAgentImage(ctx context.Context, image *Image) context.Context {\n\treturn context.WithValue(ctx, agentImageContextKey{}, image)\n}\n\nfunc GetAgentImage(ctx context.Context) *Image {\n\tif image, ok := ctx.Value(agentImageContextKey{}).(*Image); ok {\n\t\treturn image\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "integration_test/itest/logdir.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc CleanLogDir(ctx context.Context, require *Requirements, nsRx, mgrNamespace, svcNameRx string) {\n\tlogDir := filelocation.AppUserLogDir(ctx)\n\tfiles, err := os.ReadDir(logDir)\n\trequire.NoError(err)\n\tmatch := regexp.MustCompile(\n\t\tfmt.Sprintf(`^(?:traffic-manager-[0-9a-z-]+\\.%s|%s-[0-9a-z-]+\\.%s)\\.(?:log|yaml)$`,\n\t\t\tmgrNamespace, svcNameRx, nsRx))\n\n\tfor _, file := range files {\n\t\tif match.MatchString(file.Name()) {\n\t\t\tclog.Infof(ctx, \"Deleting log-file %s\", file.Name())\n\t\t\trequire.NoError(os.Remove(filepath.Join(logDir, file.Name())))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "integration_test/itest/multiple_services.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n)\n\ntype MultipleServices interface {\n\tNamespacePair\n\tName() string\n\tServiceCount() int\n}\n\ntype multipleServices struct {\n\tNamespacePair\n\tname         string\n\tserviceCount int\n}\n\nfunc WithMultipleServices(np NamespacePair, name string, serviceCount int, f func(MultipleServices)) {\n\tnp.HarnessT().Run(fmt.Sprintf(\"Test_Services_%d\", serviceCount), func(t *testing.T) {\n\t\tctx := WithT(np.HarnessContext(), t)\n\t\tms := &multipleServices{NamespacePair: np, name: name, serviceCount: serviceCount}\n\t\tms.PushHarness(ctx, ms.setup, ms.tearDown)\n\t\tdefer ms.PopHarness()\n\t\tf(ms)\n\t})\n}\n\nfunc (h *multipleServices) setup(ctx context.Context) bool {\n\twg := sync.WaitGroup{}\n\twg.Add(h.serviceCount)\n\tfor i := 0; i < h.serviceCount; i++ {\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\th.ApplyEchoService(ctx, fmt.Sprintf(\"%s-%d\", h.name, i), 80)\n\t\t}(i)\n\t}\n\twg.Wait()\n\treturn true\n}\n\nfunc (h *multipleServices) tearDown(ctx context.Context) {\n\twg := sync.WaitGroup{}\n\twg.Add(h.serviceCount)\n\tfor i := 0; i < h.serviceCount; i++ {\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\th.DeleteSvcAndWorkload(ctx, \"deploy\", fmt.Sprintf(\"hello-%d\", i))\n\t\t}(i)\n\t}\n\twg.Wait()\n}\n\nfunc (h *multipleServices) Name() string {\n\treturn h.name\n}\n\nfunc (h *multipleServices) ServiceCount() int {\n\treturn h.serviceCount\n}\n"
  },
  {
    "path": "integration_test/itest/namespace.go",
    "content": "package itest\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/labels\"\n)\n\ntype NamespacePair interface {\n\tHarness\n\tApplyApp(ctx context.Context, name, workload string)\n\tApplyEchoService(ctx context.Context, name string, port int)\n\tApplyTemplate(ctx context.Context, path string, values any)\n\tDeleteApp(ctx context.Context, name string)\n\tDeleteTemplate(ctx context.Context, path string, values any)\n\tAppNamespace() string\n\tTelepresenceConnect(ctx context.Context, args ...string) string\n\tTelepresenceTryConnect(ctx context.Context, args ...string) (string, error)\n\tDeleteSvcAndWorkload(ctx context.Context, workload, name string)\n\tKubectl(ctx context.Context, args ...string) error\n\tKubectlOk(ctx context.Context, args ...string) string\n\tKubectlOut(ctx context.Context, args ...string) (string, error)\n\tManagerNamespace() string\n\tRollbackTM(ctx context.Context)\n\tRolloutStatusWait(ctx context.Context, workload string) error\n}\n\ntype Namespaces struct {\n\tNamespace string           `json:\"namespace,omitempty\"`\n\tSelector  *labels.Selector `json:\"managedNamespaces,omitempty\"`\n}\n\ntype namespacesContextKey struct{}\n\nfunc WithNamespaces(ctx context.Context, namespaces *Namespaces) context.Context {\n\treturn context.WithValue(ctx, namespacesContextKey{}, namespaces)\n}\n\nfunc GetNamespaces(ctx context.Context) *Namespaces {\n\tif namespaces, ok := ctx.Value(namespacesContextKey{}).(*Namespaces); ok {\n\t\treturn namespaces\n\t}\n\treturn nil\n}\n\n// The namespaceSuite has no tests. It's sole purpose is to create and destroy the namespaces and\n// any non-namespaced resources that we, ourselves, make nsPair specific, such as the\n// mutating webhook configuration for the traffic-agent injection.\ntype nsPair struct {\n\tHarness\n\tNamespaces\n}\n\n// TelepresenceConnect connects using the AppNamespace and ManagerNamespace and requires the result to be OK.\nfunc (s *nsPair) TelepresenceConnect(ctx context.Context, args ...string) string {\n\treturn TelepresenceOk(ctx,\n\t\tappend(\n\t\t\t[]string{\"connect\", \"--namespace\", s.AppNamespace(), \"--manager-namespace\", s.ManagerNamespace()},\n\t\t\targs...)...)\n}\n\n// TelepresenceTryConnect connects using the AppNamespace and ManagerNamespace and returns an error on failure.\nfunc (s *nsPair) TelepresenceTryConnect(ctx context.Context, args ...string) (string, error) {\n\tstdout, _, err := Telepresence(ctx,\n\t\tappend(\n\t\t\t[]string{\"connect\", \"--namespace\", s.AppNamespace(), \"--manager-namespace\", s.ManagerNamespace()},\n\t\t\targs...)...)\n\treturn stdout, err\n}\n\nfunc WithNamespacePair(ctx context.Context, suffix string, f func(NamespacePair)) {\n\ts := &nsPair{}\n\tvar namespace string\n\tnamespace, s.Namespace = AppAndMgrNSName(suffix)\n\ts.Selector = labels.SelectorFromNames(namespace)\n\tgetT(ctx).Run(fmt.Sprintf(\"Test_Namespaces_%s\", suffix), func(t *testing.T) {\n\t\tctx = WithT(ctx, t)\n\t\tctx = WithUser(ctx, s.Namespace+\":\"+TestUser)\n\t\tctx = WithNamespaces(ctx, &s.Namespaces)\n\t\ts.Harness = NewContextHarness(ctx)\n\t\ts.PushHarness(ctx, s.setup, s.tearDown)\n\t\tdefer s.PopHarness()\n\t\tf(s)\n\t})\n}\n\nfunc (s *nsPair) setup(ctx context.Context) bool {\n\tCreateNamespaces(ctx, s.AppNamespace(), s.Namespace)\n\tt := getT(ctx)\n\tif t.Failed() {\n\t\treturn false\n\t}\n\terr := Kubectl(ctx, s.Namespace, \"apply\", \"-f\", filepath.Join(GetOSSRoot(ctx), \"testdata\", \"k8s\", \"client_sa.yaml\"))\n\tif assert.NoError(t, err, \"failed to create connect ServiceAccount\") {\n\t\tdb, err := ReadTemplate(ctx, filepath.Join(GetOSSRoot(ctx), \"testdata\", \"k8s\", \"client_rancher.goyaml\"), map[string]string{\n\t\t\t\"ManagerNamespace\": s.Namespace,\n\t\t})\n\t\tif assert.NoError(t, err) {\n\t\t\tassert.NoError(t, Kubectl(dos.WithStdin(ctx, bytes.NewReader(db)), s.Namespace, \"apply\", \"-f\", \"-\"))\n\t\t}\n\t}\n\treturn !t.Failed()\n}\n\nfunc AppAndMgrNSName(suffix string) (appNS, mgrNS string) {\n\tmgrNS = fmt.Sprintf(\"ambassador-%s\", suffix)\n\tappNS = fmt.Sprintf(\"telepresence-%s\", suffix)\n\treturn appNS, mgrNS\n}\n\nfunc (s *nsPair) tearDown(ctx context.Context) {\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tDeleteNamespaces(ctx, s.AppNamespace(), s.Namespace)\n\t}()\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\t_ = Kubectl(ctx, \"\", \"delete\", \"--wait=false\", \"mutatingwebhookconfiguration\", \"agent-injector-webhook-\"+s.Namespace)\n\t}()\n\twg.Wait()\n}\n\nfunc (s *nsPair) RollbackTM(ctx context.Context) {\n\tctx, cancel := context.WithTimeout(ctx, 5*time.Minute)\n\tdefer cancel()\n\terr := Command(ctx, \"helm\", \"rollback\", \"--no-hooks\", \"--wait\", \"--namespace\", s.ManagerNamespace(), agentconfig.ManagerAppName).Run()\n\tt := getT(ctx)\n\trequire.NoError(t, err)\n\trequire.NoError(t, RolloutStatusWait(ctx, s.Namespace, \"deploy/\"+agentconfig.ManagerAppName))\n\tassert.Eventually(t, func() bool {\n\t\treturn len(RunningPodNames(ctx, agentconfig.ManagerAppName, s.Namespace)) == 1\n\t}, 30*time.Second, 5*time.Second)\n\ts.CapturePodLogs(ctx, agentconfig.ManagerAppName, \"\", s.Namespace)\n}\n\nfunc (s *nsPair) AppNamespace() string {\n\tif len(s.Selector.MatchExpressions) == 1 {\n\t\tm := s.Selector.MatchExpressions[0]\n\t\tif m.Key == labels.NameLabelKey && m.Operator == labels.OperatorIn {\n\t\t\treturn m.Values[0]\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc (s *nsPair) ManagerNamespace() string {\n\treturn s.Namespace\n}\n\nfunc (s *nsPair) ApplyEchoService(ctx context.Context, name string, port int) {\n\tgetT(ctx).Helper()\n\tApplyEchoService(ctx, name, s.AppNamespace(), port)\n}\n\n// ApplyApp calls kubectl apply -n <namespace> -f on the given app + .yaml found in testdata/k8s relative\n// to the directory returned by GetCurrentDirectory.\nfunc (s *nsPair) ApplyApp(ctx context.Context, name, workload string) {\n\tgetT(ctx).Helper()\n\tApplyApp(ctx, name, s.AppNamespace(), workload)\n}\n\n// DeleteApp calls kubectl delete -n <namespace> -f on the given app + .yaml found in testdata/k8s relative\n// to the directory returned by GetCurrentDirectory.\nfunc (s *nsPair) DeleteApp(ctx context.Context, name string) {\n\tgetT(ctx).Helper()\n\tDeleteApp(ctx, name, s.AppNamespace())\n}\n\nfunc (s *nsPair) RolloutStatusWait(ctx context.Context, workload string) error {\n\treturn RolloutStatusWait(ctx, s.AppNamespace(), workload)\n}\n\nfunc (s *nsPair) DeleteSvcAndWorkload(ctx context.Context, workload, name string) {\n\tgetT(ctx).Helper()\n\tDeleteSvcAndWorkload(ctx, workload, name, s.AppNamespace())\n}\n\nfunc (s *nsPair) ApplyTemplate(ctx context.Context, path string, values any) {\n\ts.doWithTemplate(ctx, \"apply\", path, values)\n}\n\nfunc (s *nsPair) DeleteTemplate(ctx context.Context, path string, values any) {\n\tyml, err := ReadTemplate(ctx, path, values)\n\trequire.NoError(getT(ctx), err)\n\tif err = s.Kubectl(dos.WithStdin(ctx, bytes.NewReader(yml)), \"delete\", \"-f\", \"-\"); err != nil {\n\t\tclog.Errorf(ctx, \"unable to delete %q\", string(yml))\n\t\tgetT(ctx).Fatal(err)\n\t}\n}\n\nfunc (s *nsPair) doWithTemplate(ctx context.Context, action, path string, values any) {\n\tyml, err := ReadTemplate(ctx, path, values)\n\trequire.NoError(getT(ctx), err)\n\tif err = s.Kubectl(dos.WithStdin(ctx, bytes.NewReader(yml)), action, \"-f\", \"-\"); err != nil {\n\t\tclog.Errorf(ctx, \"unable to %s %q\", action, string(yml))\n\t\tgetT(ctx).Fatal(err)\n\t}\n}\n\n// Kubectl runs kubectl with the default context and the application namespace.\nfunc (s *nsPair) Kubectl(ctx context.Context, args ...string) error {\n\tgetT(ctx).Helper()\n\treturn Kubectl(ctx, s.AppNamespace(), args...)\n}\n\n// KubectlOk runs kubectl with the default context and the application namespace and returns its combined output\n// and fails if an error occurred.\nfunc (s *nsPair) KubectlOk(ctx context.Context, args ...string) string {\n\tout, err := KubectlOut(ctx, s.AppNamespace(), args...)\n\trequire.NoError(getT(ctx), err)\n\treturn out\n}\n\n// KubectlOut runs kubectl with the default context and the application namespace and returns its combined output.\nfunc (s *nsPair) KubectlOut(ctx context.Context, args ...string) (string, error) {\n\tgetT(ctx).Helper()\n\treturn KubectlOut(ctx, s.AppNamespace(), args...)\n}\n"
  },
  {
    "path": "integration_test/itest/runner.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/alexflint/go-filemutex\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype Runner interface {\n\tAddClusterSuite(func(context.Context) TestingSuite)\n\tAddNamespacePairSuite(suffix string, f func(NamespacePair) TestingSuite)\n\tAddTrafficManagerSuite(suffix string, f func(TrafficManager) TestingSuite)\n\tAddConnectedSuite(suffix string, f func(TrafficManager) TestingSuite)\n\tAddMultipleServicesSuite(suffix, name string, svcCount int, f func(MultipleServices) TestingSuite)\n\tAddSingleServiceSuite(suffix, name string, f func(SingleService) TestingSuite)\n\tRunTests(context.Context)\n}\n\ntype namedRunner struct {\n\twithMultipleServices []func(MultipleServices) TestingSuite\n\twithSingleService    []func(SingleService) TestingSuite\n}\n\ntype runnerKey struct {\n\tname     string\n\tsvcCount int\n}\n\ntype suffixedRunner struct {\n\twithNamespace      []func(NamespacePair) TestingSuite\n\twithTrafficManager []func(TrafficManager) TestingSuite\n\twithConnected      []func(TrafficManager) TestingSuite\n\twithName           map[runnerKey]*namedRunner\n}\n\ntype runner struct {\n\twithCluster []func(ctx context.Context) TestingSuite\n\twithSuffix  map[string]*suffixedRunner\n}\n\nvar defaultRunner Runner = &runner{withSuffix: make(map[string]*suffixedRunner)} //nolint:gochecknoglobals // integration test config\n\n// AddClusterSuite adds a constructor for a test suite that requires a cluster to run to the default runner.\nfunc AddClusterSuite(f func(context.Context) TestingSuite) {\n\tdefaultRunner.AddClusterSuite(f)\n}\n\n// AddClusterSuite adds a constructor for a test suite that requires a cluster to run.\nfunc (r *runner) AddClusterSuite(f func(context.Context) TestingSuite) {\n\tr.withCluster = append(r.withCluster, f)\n}\n\nfunc (r *runner) forSuffix(suffix string) *suffixedRunner {\n\tsr, ok := r.withSuffix[suffix]\n\tif !ok {\n\t\tsr = &suffixedRunner{withName: map[runnerKey]*namedRunner{}}\n\t\tr.withSuffix[suffix] = sr\n\t}\n\treturn sr\n}\n\n// AddNamespacePairSuite adds a constructor for a test suite that requires a cluster where a namespace\n// pair has been initialized to the default runner.\nfunc AddNamespacePairSuite(suffix string, f func(NamespacePair) TestingSuite) {\n\tdefaultRunner.AddNamespacePairSuite(suffix, f)\n}\n\n// AddNamespacePairSuite adds a constructor for a test suite that requires a cluster where a namespace\n// pair has been initialized.\nfunc (r *runner) AddNamespacePairSuite(suffix string, f func(NamespacePair) TestingSuite) {\n\tsr := r.forSuffix(suffix)\n\tsr.withNamespace = append(sr.withNamespace, f)\n}\n\n// AddTrafficManagerSuite adds a constructor for a test suite that requires a cluster where a namespace\n// pair has been initialized and a traffic manager is installed.\nfunc AddTrafficManagerSuite(suffix string, f func(manager TrafficManager) TestingSuite) {\n\tdefaultRunner.AddTrafficManagerSuite(suffix, f)\n}\n\n// AddTrafficManagerSuite adds a constructor for a test suite that requires a cluster where a namespace\n// pair has been initialized and a traffic manager is installed.\nfunc (r *runner) AddTrafficManagerSuite(suffix string, f func(TrafficManager) TestingSuite) {\n\tsr := r.forSuffix(suffix)\n\tsr.withTrafficManager = append(sr.withTrafficManager, f)\n}\n\nfunc (r *suffixedRunner) forName(key runnerKey) *namedRunner {\n\tnr, ok := r.withName[key]\n\tif !ok {\n\t\tnr = &namedRunner{}\n\t\tr.withName[key] = nr\n\t}\n\treturn nr\n}\n\n// AddConnectedSuite adds a constructor for a test suite to the default runner that requires a cluster where a namespace\n// pair has been initialized, and telepresence is connected.\nfunc AddConnectedSuite(suffix string, f func(TrafficManager) TestingSuite) {\n\tdefaultRunner.AddConnectedSuite(suffix, f)\n}\n\n// AddConnectedSuite adds a constructor for a test suite to the default runner that requires a cluster where a namespace\n// pair has been initialized, and telepresence is connected.\nfunc (r *runner) AddConnectedSuite(suffix string, f func(TrafficManager) TestingSuite) {\n\tsr := r.forSuffix(suffix)\n\tsr.withConnected = append(sr.withConnected, f)\n}\n\n// AddMultipleServicesSuite adds a constructor for a test suite to the default runner that requires a cluster where a namespace\n// pair has been initialized, multiple services have been installed, and telepresence is connected.\nfunc AddMultipleServicesSuite(suffix, name string, svcCount int, f func(services MultipleServices) TestingSuite) {\n\tdefaultRunner.AddMultipleServicesSuite(suffix, name, svcCount, f)\n}\n\n// AddMultipleServicesSuite adds a constructor for a test suite that requires a cluster where a namespace\n// pair has been initialized, multiple services have been installed, and telepresence is connected.\nfunc (r *runner) AddMultipleServicesSuite(suffix, name string, svcCount int, f func(services MultipleServices) TestingSuite) {\n\tnr := r.forSuffix(suffix).forName(runnerKey{name: name, svcCount: svcCount})\n\tnr.withMultipleServices = append(nr.withMultipleServices, f)\n}\n\n// AddSingleServiceSuite adds a constructor for a test suite to the default runner that requires a cluster where a namespace\n// pair has been initialized, a service has been installed, and telepresence is connected.\nfunc AddSingleServiceSuite(suffix, name string, f func(services SingleService) TestingSuite) {\n\tdefaultRunner.AddSingleServiceSuite(suffix, name, f)\n}\n\n// AddSingleServiceSuite adds a constructor for a test suite that requires a cluster where a namespace\n// pair has been initialized, a service has been installed, and telepresence is connected.\nfunc (r *runner) AddSingleServiceSuite(suffix, name string, f func(services SingleService) TestingSuite) {\n\tnr := r.forSuffix(suffix).forName(runnerKey{name: name})\n\tnr.withSingleService = append(nr.withSingleService, f)\n}\n\nfunc RunTests(c context.Context) {\n\tdefaultRunner.RunTests(c)\n}\n\n// RunTests creates all suites using the added constructors and runs them.\nfunc (r *runner) RunTests(c context.Context) { //nolint:gocognit\n\tc = LoadEnvAndConfig(c)\n\tm, err := filemutex.New(filepath.Join(os.TempDir(), \"telepresence-itest.lock\"))\n\tif err != nil {\n\t\trequire.NoError(getT(c), err)\n\t}\n\terr = m.Lock() // Will block until lock can be acquired\n\tif err != nil {\n\t\trequire.NoError(getT(c), err)\n\t}\n\tdefer func() {\n\t\t_ = m.Unlock()\n\t}()\n\n\tWithCluster(c, func(c context.Context) {\n\t\tfunc() {\n\t\t\tt := getT(c)\n\t\t\tfor _, f := range r.withCluster {\n\t\t\t\ts := f(c)\n\t\t\t\tif suiteEnabled(c, s) {\n\t\t\t\t\tt.Run(s.SuiteName(), func(t *testing.T) {\n\t\t\t\t\t\tts := f(c)\n\t\t\t\t\t\tts.setContext(ts.AmendSuiteContext(c))\n\t\t\t\t\t\tsuite.Run(t, ts)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t\tfor s, sr := range r.withSuffix {\n\t\t\tWithNamespacePair(c, GetGlobalHarness(c).Suffix()+s, func(np NamespacePair) {\n\t\t\t\tfor _, f := range sr.withNamespace {\n\t\t\t\t\tnp.RunSuite(f(np))\n\t\t\t\t}\n\t\t\t\tif len(sr.withTrafficManager)+len(sr.withConnected)+len(sr.withName) > 0 {\n\t\t\t\t\tWithTrafficManager(np, func(c context.Context, cnp TrafficManager) {\n\t\t\t\t\t\tfor _, f := range sr.withTrafficManager {\n\t\t\t\t\t\t\tcnp.RunSuite(f(cnp))\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif len(sr.withConnected)+len(sr.withName) > 0 {\n\t\t\t\t\t\t\tWithConnected(cnp, func(c context.Context, cnp TrafficManager) {\n\t\t\t\t\t\t\t\tfor _, f := range sr.withConnected {\n\t\t\t\t\t\t\t\t\tcnp.RunSuite(f(cnp))\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tfor n, nr := range sr.withName {\n\t\t\t\t\t\t\t\t\tif len(nr.withMultipleServices) > 0 {\n\t\t\t\t\t\t\t\t\t\tWithMultipleServices(cnp, n.name, n.svcCount, func(ms MultipleServices) {\n\t\t\t\t\t\t\t\t\t\t\tfor _, f := range nr.withMultipleServices {\n\t\t\t\t\t\t\t\t\t\t\t\tms.RunSuite(f(ms))\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif len(nr.withSingleService) > 0 {\n\t\t\t\t\t\t\t\t\t\tWithSingleService(cnp, n.name, func(ss SingleService) {\n\t\t\t\t\t\t\t\t\t\t\tfor _, f := range nr.withSingleService {\n\t\t\t\t\t\t\t\t\t\t\t\tss.RunSuite(f(ss))\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "integration_test/itest/single_service.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype SingleService interface {\n\tNamespacePair\n\tServiceName() string\n}\n\ntype singleService struct {\n\tNamespacePair\n\tserviceName string\n}\n\nfunc WithSingleService(h NamespacePair, serviceName string, f func(SingleService)) {\n\th.HarnessT().Run(fmt.Sprintf(\"Test_Service_%s\", serviceName), func(t *testing.T) {\n\t\tctx := WithT(h.HarnessContext(), t)\n\t\ts := &singleService{NamespacePair: h, serviceName: serviceName}\n\t\ts.PushHarness(ctx, s.setup, s.tearDown)\n\t\tdefer h.PopHarness()\n\t\tf(s)\n\t})\n}\n\nfunc (h *singleService) setup(ctx context.Context) bool {\n\th.ApplyEchoService(ctx, h.serviceName, 80)\n\treturn true\n}\n\nfunc (h *singleService) tearDown(ctx context.Context) {\n\th.DeleteSvcAndWorkload(ctx, \"deploy\", h.serviceName)\n}\n\nfunc (h *singleService) ServiceName() string {\n\treturn h.serviceName\n}\n"
  },
  {
    "path": "integration_test/itest/status.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/cmd\"\n)\n\ntype StatusResponse struct {\n\tRootDaemon          *cmd.RootDaemonStatus          `json:\"root_daemon,omitempty\"`\n\tUserDaemon          *cmd.UserDaemonStatus          `json:\"user_daemon,omitempty\"`\n\tTrafficManager      *cmd.TrafficManagerStatus      `json:\"traffic_manager,omitempty\"`\n\tContainerizedDaemon *cmd.ContainerizedDaemonStatus `json:\"daemon,omitempty\"`\n\tConnections         []struct {\n\t\tRootDaemon          *cmd.RootDaemonStatus          `json:\"root_daemon,omitempty\"`\n\t\tUserDaemon          *cmd.UserDaemonStatus          `json:\"user_daemon,omitempty\"`\n\t\tTrafficManager      *cmd.TrafficManagerStatus      `json:\"traffic_manager,omitempty\"`\n\t\tContainerizedDaemon *cmd.ContainerizedDaemonStatus `json:\"daemon,omitempty\"`\n\t} `json:\"connections,omitempty\"`\n\tError string `json:\"err,omitempty\"`\n}\n\nfunc TelepresenceStatus(ctx context.Context, args ...string) (*StatusResponse, error) {\n\tstdout, stderr, err := Telepresence(ctx, append([]string{\"status\", \"--output\", \"json\"}, args...)...)\n\tvar status StatusResponse\n\tjErr := json.Unmarshal([]byte(stdout), &status)\n\tif err != nil {\n\t\tif jErr == nil && status.Error != \"\" {\n\t\t\tclog.Error(ctx, status.Error)\n\t\t\treturn nil, errors.New(status.Error)\n\t\t}\n\t\tclog.Error(ctx, stderr)\n\t\treturn nil, err\n\t}\n\tif jErr != nil {\n\t\treturn nil, jErr\n\t}\n\tif cd := status.ContainerizedDaemon; cd != nil {\n\t\tif cd.RoutingSnake == nil {\n\t\t\tcd.RoutingSnake = &client.RoutingSnake{}\n\t\t}\n\t\tstatus.UserDaemon = cd.UserDaemonStatus\n\t\tstatus.RootDaemon = &cmd.RootDaemonStatus{\n\t\t\tRunning:      cd.Running,\n\t\t\tName:         cd.Name,\n\t\t\tVersion:      cd.Version,\n\t\t\tDNS:          cd.DNS,\n\t\t\tRoutingSnake: cd.RoutingSnake,\n\t\t\tPortMappings: cd.PortMappings,\n\t\t}\n\t} else if status.RootDaemon == nil {\n\t\tstatus.RootDaemon = &cmd.RootDaemonStatus{RoutingSnake: &client.RoutingSnake{}}\n\t}\n\treturn &status, nil\n}\n\nfunc TelepresenceStatusOk(ctx context.Context, args ...string) *StatusResponse {\n\tstatus, err := TelepresenceStatus(ctx, args...)\n\trequire.NoError(getT(ctx), err)\n\treturn status\n}\n"
  },
  {
    "path": "integration_test/itest/suite.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\n\t\"github.com/stretchr/testify/suite\"\n)\n\ntype TestingSuite interface {\n\tsuite.TestingSuite\n\tHarness\n\tAmendSuiteContext(context.Context) context.Context\n\tContext() context.Context\n\tAssert() *Assertions\n\tRequire() *Requirements\n\tSuiteName() string\n\tsetContext(ctx context.Context)\n}\n\ntype Suite struct {\n\tsuite.Suite\n\tHarness\n\tctx context.Context\n}\n\nfunc (s *Suite) AmendSuiteContext(ctx context.Context) context.Context {\n\treturn ctx\n}\n\n//nolint:unused // Linter is confused about this one.\nfunc (s *Suite) setContext(ctx context.Context) {\n\ts.ctx = ctx\n}\n\nfunc (s *Suite) Context() context.Context {\n\treturn WithT(s.ctx, s.T())\n}\n\nfunc (s *Suite) Assert() *Assertions {\n\treturn &Assertions{Assertions: s.Suite.Assert()}\n}\n\nfunc (s *Suite) Require() *Requirements {\n\treturn &Requirements{Assertions: s.Suite.Require()}\n}\n"
  },
  {
    "path": "integration_test/itest/tempdir.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"sync/atomic\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype tempDirBase struct {\n\ttempDir    string\n\ttempDirSeq uint64\n}\n\ntype tempDirBaseKey struct{}\n\nfunc withTempDirBase(ctx context.Context, td *tempDirBase) context.Context {\n\treturn context.WithValue(ctx, tempDirBaseKey{}, td)\n}\n\n// TempDir returns a temporary directory for the test to use.\n// The directory is automatically removed when the test and\n// all its subtests complete.\n// Each subsequent call to t.TempDir returns a unique directory;\n// if the directory creation fails, TempDir terminates the test by calling Fatal.\nfunc TempDir(ctx context.Context) string {\n\tt := getT(ctx)\n\tif td, ok := ctx.Value(tempDirBaseKey{}).(*tempDirBase); ok {\n\t\tseq := atomic.AddUint64(&td.tempDirSeq, 1)\n\t\tdir := fmt.Sprintf(\"%s%c%03d\", td.tempDir, os.PathSeparator, seq)\n\t\trequire.NoError(t, os.Mkdir(dir, 0o777))\n\t\treturn dir\n\t}\n\treturn t.TempDir()\n}\n"
  },
  {
    "path": "integration_test/itest/template.go",
    "content": "package itest\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/Masterminds/sprig/v3\"\n\tcore \"k8s.io/api/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\ntype TplResource interface {\n\tApply(context context.Context, namespace string) error\n\tDelete(ctx context.Context) error\n}\n\ntype ContainerPort struct {\n\tNumber   int\n\tName     string\n\tProtocol core.Protocol\n}\n\ntype ServicePort struct {\n\tNumber     int\n\tName       string\n\tProtocol   core.Protocol\n\tTargetPort string\n}\n\ntype tplBase struct {\n\tyml []byte\n\tns  string\n}\n\nfunc (b *tplBase) loadAndApply(ctx context.Context, path, ns string, data any) error {\n\tyml, err := ReadTemplate(ctx, filepath.Join(\"testdata\", \"k8s\", path+\".goyaml\"), data)\n\tif err == nil {\n\t\tb.yml = yml\n\t\tb.ns = ns\n\t\terr = Kubectl(dos.WithStdin(ctx, bytes.NewReader(b.yml)), b.ns, \"apply\", \"-f\", \"-\")\n\t}\n\treturn err\n}\n\nfunc (b *tplBase) Delete(ctx context.Context) error {\n\treturn Kubectl(dos.WithStdin(ctx, bytes.NewReader(b.yml)), b.ns, \"delete\", \"-f\", \"-\")\n}\n\ntype Generic struct {\n\ttplBase\n\tName           string\n\tAnnotations    map[string]string\n\tLabels         map[string]string\n\tEnvironment    []core.EnvVar\n\tTargetPort     string\n\tServicePorts   []ServicePort\n\tContainerPort  int\n\tContainerPorts []ContainerPort\n\tImage          string\n\tRegistry       string\n\tServiceAccount string\n\tVolumes        []core.Volume\n\tVolumeMounts   []core.VolumeMount\n}\n\nfunc (g *Generic) Apply(ctx context.Context, ns string) error {\n\treturn g.loadAndApply(ctx, \"generic\", ns, g)\n}\n\ntype PersistentVolume struct {\n\ttplBase\n\tName             string\n\tAnnotations      map[string]string\n\tStorageClassName string\n}\n\nfunc (p *PersistentVolume) Apply(ctx context.Context, ns string) error {\n\treturn p.loadAndApply(ctx, \"pv\", ns, p)\n}\n\ntype PersistentVolumeClaim struct {\n\ttplBase\n\tName             string\n\tAnnotations      map[string]string\n\tStorageClassName string\n}\n\nfunc (r *PersistentVolumeClaim) Apply(ctx context.Context, ns string) error {\n\treturn r.loadAndApply(ctx, \"pvc\", ns, r)\n}\n\ntype DisruptionBudget struct {\n\tName           string\n\tMinAvailable   int\n\tMaxUnavailable int\n}\n\nfunc OpenTemplate(ctx context.Context, path string, data any) (io.Reader, error) {\n\tb, err := ReadTemplate(ctx, path, data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bytes.NewReader(b), nil\n}\n\nfunc ReadTemplate(ctx context.Context, path string, data any) ([]byte, error) {\n\tfnMap := sprig.FuncMap()\n\tfnMap[\"toYaml\"] = toYAML\n\ttpl, err := template.New(\"\").Funcs(fnMap).ParseFiles(path)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\twr := bytes.Buffer{}\n\tif err = tpl.ExecuteTemplate(&wr, filepath.Base(path), data); err != nil {\n\t\treturn nil, err\n\t}\n\treturn wr.Bytes(), nil\n}\n\nfunc EvalTemplate(content string, data any) ([]byte, error) {\n\tfnMap := sprig.FuncMap()\n\tfnMap[\"toYaml\"] = toYAML\n\ttpl, err := template.New(\"embedded\").Funcs(fnMap).Parse(content)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\twr := bytes.Buffer{}\n\tif err = tpl.ExecuteTemplate(&wr, \"embedded\", data); err != nil {\n\t\treturn nil, err\n\t}\n\treturn wr.Bytes(), nil\n}\n\n// toYAML is direct copy of toYaml in the helm.sh/helm/v3/pkg/engine package.\nfunc toYAML(v interface{}) string {\n\tdata, err := yaml.Marshal(v)\n\tif err != nil {\n\t\t// Swallow errors inside of a template.\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSuffix(string(data), \"\\n\")\n}\n"
  },
  {
    "path": "integration_test/itest/timed.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"time\"\n)\n\n// TimedRun gives the given function maxDuration time to finish, and returns a timeout error if it doesn't. Otherwise,\n// it returns the error from the function.\nfunc TimedRun(ctx context.Context, maxDuration time.Duration, function func(ctx2 context.Context) error) error {\n\tctx, cancel := context.WithTimeout(ctx, maxDuration)\n\tdefer cancel()\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\tdefault:\n\t\treturn function(ctx)\n\t}\n}\n"
  },
  {
    "path": "integration_test/itest/traffic_manager.go",
    "content": "package itest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/portforward\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/userd/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\tgrpcClient \"github.com/telepresenceio/telepresence/v2/pkg/grpc/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/server\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n)\n\ntype TrafficManager interface {\n\tNamespacePair\n\tDoWithTrafficManager(context.Context, func(context.Context, context.CancelFunc, manager.ManagerClient, *manager.SessionInfo)) error\n\tDoWithSession(context.Context, *rpc.ConnectRequest, func(context.Context, rpc.ConnectorServer)) error\n\tNewConnectRequest(context.Context) *rpc.ConnectRequest\n}\n\ntype trafficManager struct {\n\tNamespacePair\n}\n\nfunc WithTrafficManager(np NamespacePair, f func(ctx context.Context, ch TrafficManager)) {\n\tnp.HarnessT().Run(\"Test_TrafficManager\", func(t *testing.T) {\n\t\tctx := WithT(np.HarnessContext(), t)\n\t\trequire.NoError(t, np.GeneralError())\n\t\tth := &trafficManager{NamespacePair: np}\n\t\tth.PushHarness(ctx, th.setup, th.tearDown)\n\t\tdefer th.PopHarness()\n\t\tf(ctx, th)\n\t})\n}\n\nfunc (th *trafficManager) setup(ctx context.Context) bool {\n\tt := getT(ctx)\n\tTelepresenceQuitOk(ctx)\n\t_, err := th.TelepresenceHelmInstall(ctx, false)\n\treturn assert.NoError(t, err)\n}\n\nfunc (th *trafficManager) tearDown(ctx context.Context) {\n\tth.UninstallTrafficManager(ctx, th.ManagerNamespace())\n}\n\nfunc (th *trafficManager) trafficManagerConnection(ctx context.Context) (*grpc.ClientConn, error) {\n\tcfg, err := clientcmd.BuildConfigFromFlags(\"\", KubeConfig(ctx))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn dialTrafficManager(ctx, cfg, th.ManagerNamespace())\n}\n\nfunc dialTrafficManager(ctx context.Context, cfg *rest.Config, managerNamespace string) (*grpc.ClientConn, error) {\n\tk8sApi, err := kubernetes.NewForConfig(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctx = k8sapi.WithK8sInterface(ctx, k8sApi)\n\tctx = portforward.WithRestConfig(ctx, cfg)\n\tpap, err := portforward.ResolveSvcToPod(ctx, \"traffic-manager\", managerNamespace, \"8081\")\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"cannot resolve svc/traffic-manager.%s:8081: %v\", managerNamespace, err)\n\t\treturn nil, err\n\t}\n\treturn grpcClient.DialGRPC(ctx, fmt.Sprintf(portforward.K8sPFScheme+\":///svc/traffic-manager.%s:8081\", managerNamespace),\n\t\tgrpc.WithResolvers(portforward.NewResolver(ctx, pap)),\n\t\tgrpc.WithContextDialer(portforward.Dialer(ctx)),\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t)\n}\n\n// DoWithTrafficManager is intended to be used when testing the traffic-manager grpc. It simulates a connector client\n// that has a session established with the traffic-manager.\nfunc (th *trafficManager) DoWithTrafficManager(ctx context.Context, f func(context.Context, context.CancelFunc, manager.ManagerClient, *manager.SessionInfo)) error {\n\tconn, err := th.trafficManagerConnection(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\n\tmgr := manager.NewManagerClient(conn)\n\n\t// Retrieve the session info from the traffic-manager. This is how\n\t// a connection to a namespace is made. The traffic-manager now\n\t// associates the returned session with that namespace in subsequent\n\t// calls.\n\tclientSession, err := mgr.ArriveAsClient(ctx, &manager.ClientInfo{\n\t\tName:      \"telepresence@datawire.io\",\n\t\tNamespace: th.AppNamespace(),\n\t\tInstallId: \"xxx\",\n\t\tProduct:   \"telepresence\",\n\t\tVersion:   th.ClientVersion().String(),\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Normal ticker routine to keep the client alive.\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tgo func() {\n\t\tticker := time.NewTicker(5 * time.Second)\n\t\tdefer ticker.Stop()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ticker.C:\n\t\t\t\t_, _ = mgr.Remain(ctx, &manager.RemainRequest{Session: clientSession})\n\t\t\tcase <-ctx.Done():\n\t\t\t\t_, _ = mgr.Depart(ctx, clientSession)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\tf(ctx, cancel, mgr, clientSession)\n\treturn nil\n}\n\n// NewConnectRequest returns a connector.ConnectRequest that has been initialized with default values. It's intended\n// to be used in together with DoWithSession to provide an ability to create and modify the request used when\n// connecting.\nfunc (th *trafficManager) NewConnectRequest(ctx context.Context) *rpc.ConnectRequest {\n\tflags := map[string]string{\n\t\t\"kubeconfig\": KubeConfig(ctx),\n\t\t\"namespace\":  th.AppNamespace(),\n\t}\n\tif user := GetUser(ctx); user != \"default\" {\n\t\tflags[\"as\"] = \"system:serviceaccount:\" + user\n\t}\n\treturn &rpc.ConnectRequest{\n\t\tKubeFlags:        flags,\n\t\tMappedNamespaces: []string{th.AppNamespace()},\n\t\tManagerNamespace: th.ManagerNamespace(),\n\t\tEnvironment:      th.GlobalEnv(ctx),\n\t}\n}\n\n// DoWithSession is intended to be used when testing the connector GRPC directly without using the CLI. A \"connect\" is\n// made before calling the provided function, which means that the `rpc.ConnectorServer` can be used as a connected daemon. A\n// call to quit is guaranteed after the function ends.\nfunc (th *trafficManager) DoWithSession(ctx context.Context, cr *rpc.ConnectRequest, f func(context.Context, rpc.ConnectorServer)) error {\n\tclient.ProcessName = func() string {\n\t\treturn client.UserDaemonName\n\t}\n\tctx = docker.EnableClient(ctx)\n\tctx = cli.InitContext(ctx)\n\tcfg := client.GetConfig(ctx)\n\tlogFile := filepath.Join(filelocation.AppUserLogDir(ctx), \"connector.log\")\n\tctx, err := logging.InitContext(ctx, logFile, cfg.LogLevels().UserDaemon, logging.RotateNever, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx = dos.WithExe(ctx, th)\n\terr = connect.EnsureRootDaemonRunning(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tg := log.NewGroup(ctx)\n\n\tgrpcServer := grpc.NewServer()\n\tsrv := daemon.NewService(ctx, cancel, client.GetConfig(ctx), grpcServer)\n\n\tif cfg.Intercept().UseFtp && !srv.LinkedFTP() {\n\t\tg.Go(\"fuseftp-server\", func(ctx context.Context) error {\n\t\t\tif err := srv.InitFTPServer(ctx); err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t\t<-ctx.Done()\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tlc := net.ListenConfig{}\n\tfp, err := ioutil.FreePortsTCP(1)\n\tif err != nil {\n\t\treturn err\n\t}\n\taddr := fp[0]\n\tgrpcListener, err := lc.Listen(ctx, \"tcp\", addr.String())\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer grpcListener.Close()\n\tsrv.SetListenerAddress(addr)\n\n\tg.Go(\"server-grpc\", func(ctx context.Context) error {\n\t\treturn server.Serve(ctx, grpcServer, grpcListener)\n\t})\n\n\tsv := srv.ConnectorServer()\n\t_, err = sv.Connect(ctx, cr)\n\tif err != nil {\n\t\treturn tpGrpc.FromGRPC(err)\n\t}\n\tfunc() {\n\t\tdefer func() {\n\t\t\t_, _ = sv.Quit(ctx, nil)\n\t\t}()\n\t\tf(ctx, sv)\n\t}()\n\treturn g.Wait()\n}\n"
  },
  {
    "path": "integration_test/kubeauth_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc (s *notConnectedSuite) Test_ConnectWithKubeconfigExec() {\n\textContext, cfg := s.makeKubeConfigWithExec()\n\tconnectWithExec := func(ctx context.Context, useDocker bool) {\n\t\tif useDocker && s.IsCI() {\n\t\t\tif !(runtime.GOOS == \"linux\" && runtime.GOARCH == \"amd64\") {\n\t\t\t\ts.T().Skip(\"CI can't run linux docker containers inside non-linux runners\")\n\t\t\t}\n\t\t}\n\n\t\t// Retrieve the current size of the connector.log so that we can scan the messages that appear after connect\n\t\trq := s.Require()\n\t\tlogSize := int64(0)\n\t\tlogName := \"connector.log\"\n\t\tif useDocker {\n\t\t\t// Authenticator runs as a separate process on the host\n\t\t\tlogName = \"kubeauth.log\"\n\t\t}\n\t\tlogFQName := filepath.Join(filelocation.AppUserLogDir(ctx), logName)\n\t\tst, err := os.Stat(logFQName)\n\t\tif err == nil {\n\t\t\tlogSize = st.Size()\n\t\t} else if !errors.Is(err, os.ErrNotExist) {\n\t\t\trq.FailNow(err.Error())\n\t\t}\n\n\t\tctx = itest.WithKubeConfig(ctx, cfg)\n\n\t\tvar args []string\n\t\tif useDocker {\n\t\t\targs = []string{\"--docker\"}\n\t\t}\n\t\t_, err = s.TelepresenceTryConnect(ctx, args...)\n\t\trq.NoError(err)\n\t\tdefer itest.TelepresenceQuitOk(ctx)\n\n\t\t// Scan the log from its previous end. It should now contain a message indicating that the gRPC service that\n\t\t// it contains have served an exec request from a modified kubeconfig requesting credentials from extContext.\n\t\tlogF, err := os.Open(logFQName)\n\t\trq.NoError(err)\n\t\tdefer logF.Close()\n\t\tif logSize > 0 {\n\t\t\t_, err = logF.Seek(logSize, 0)\n\t\t\trq.NoError(err)\n\t\t}\n\t\tscn := bufio.NewScanner(logF)\n\t\tfound := false\n\t\tfor !found && scn.Scan() {\n\t\t\tfound = strings.Contains(scn.Text(), \"GetContextExecCredentials(\"+extContext+\")\")\n\t\t}\n\t\trq.Truef(found, \"unable to find expected GetContextExecCredentials in the %s\", logName)\n\t}\n\ts.Run(\"root-daemon\", func() { connectWithExec(s.Context(), false) })\n\ts.Run(\"containerized-daemon\", func() {\n\t\tctx := itest.WithConfig(s.Context(), func(config client.Config) {\n\t\t\tconfig.Intercept().UseFtp = false\n\t\t})\n\t\tconnectWithExec(ctx, true)\n\t})\n}\n\nfunc (s *notConnectedSuite) makeKubeConfigWithExec() (string, *api.Config) {\n\tctx := s.Context()\n\trq := s.Require()\n\tkc := itest.KubeConfig(ctx)\n\tcfg, err := clientcmd.LoadFromFile(kc)\n\trq.NoError(err)\n\n\t// Create an additional context that has a user with an exec extension. The extension calls the k8screds program\n\t// to retrieve the credentials for the original context.\n\trq.NotEmpty(cfg.CurrentContext)\n\tcc := cfg.Contexts[cfg.CurrentContext]\n\trq.NotNil(cc)\n\trq.NotEmpty(cc.AuthInfo)\n\tai := cfg.AuthInfos[cc.AuthInfo]\n\trq.NotNil(ai)\n\tif ai.Exec != nil {\n\t\ts.T().Skipf(\"this test requires a kubecontext that doesn't have an exec extension\")\n\t}\n\n\t// Ensure that the k8screds program is built and ready.\n\tbinDir := itest.TempDir(ctx)\n\tk8sCredsBinary := filepath.Join(binDir, \"k8screds\")\n\tif runtime.GOOS == \"windows\" {\n\t\tk8sCredsBinary += \".exe\"\n\t}\n\trq.NoError(itest.Run(ctx, \"go\", \"build\", \"-o\", k8sCredsBinary, filepath.Join(\"testdata\", \"k8screds\", \"main.go\")))\n\n\t// Create a new AuthInfo.\n\textAuthInfo := cc.AuthInfo + \"-exec\"\n\trq.Nil(cfg.AuthInfos[extAuthInfo])\n\n\tenvMap := itest.EnvironMap(ctx)\n\tenvVars := make([]api.ExecEnvVar, len(envMap))\n\ti := 0\n\tfor k, v := range envMap {\n\t\tenvVars[i].Name = k\n\t\tenvVars[i].Value = v\n\t\ti++\n\t}\n\n\tcfg.AuthInfos[extAuthInfo] = &api.AuthInfo{\n\t\tExec: &api.ExecConfig{\n\t\t\tCommand:          k8sCredsBinary,\n\t\t\tArgs:             []string{cfg.CurrentContext},\n\t\t\tEnv:              envVars,\n\t\t\tAPIVersion:       \"client.authentication.k8s.io/v1beta1\",\n\t\t\tInteractiveMode:  \"Never\",\n\t\t\tStdinUnavailable: true,\n\t\t},\n\t}\n\n\textCc := cc.DeepCopy()\n\textCc.AuthInfo = extAuthInfo\n\n\t// Create a new Context that uses the new AuthInfo. We use a nasty name here to ensure\n\t// that it is correctly converted to a usable name.\n\textContext := \"abc:def/xyz$32-#1?efd\"\n\trq.Nil(cfg.Contexts[extContext])\n\n\tcfg.Contexts[extContext] = extCc\n\tcfg.CurrentContext = extContext\n\treturn extContext, cfg\n}\n"
  },
  {
    "path": "integration_test/kubeconfig_extension_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/routing\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/slice\"\n)\n\nfunc getClusterIPs(cluster *api.Cluster) ([]netip.Addr, error) {\n\tvar as []netip.Addr\n\tsvcUrl, err := url.Parse(cluster.Server)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thostname := svcUrl.Hostname()\n\tif rawIP, err := netip.ParseAddr(hostname); err == nil {\n\t\tas = []netip.Addr{rawIP}\n\t} else {\n\t\tips, err := net.LookupIP(hostname)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tas = make([]netip.Addr, len(ips))\n\t\tfor i, ip := range ips {\n\t\t\tas[i], _ = netip.AddrFromSlice(ip)\n\t\t}\n\t}\n\treturn as, nil\n}\n\nfunc (s *notConnectedSuite) Test_APIServerIsProxied() {\n\tctx := s.Context()\n\trequire := s.Require()\n\tdefaultGW, err := routing.DefaultRoute(ctx)\n\trequire.NoError(err)\n\tvar ips []netip.Addr\n\n\tctx = itest.WithKubeConfigExtension(ctx, func(cluster *api.Cluster) map[string]any {\n\t\tvar apiServers []string\n\t\tvar err error\n\t\tips, err = getClusterIPs(cluster)\n\t\trequire.NoError(err)\n\t\tfor _, ip := range ips {\n\t\t\tif ip.IsLoopback() {\n\t\t\t\ts.T().Skipf(\"test can't run on host with a loopback cluster IP %s\", ip)\n\t\t\t}\n\t\t\tif ip.Is6() {\n\t\t\t\tapiServers = append(apiServers, fmt.Sprintf(`%s/96`, ip))\n\t\t\t} else {\n\t\t\t\tapiServers = append(apiServers, fmt.Sprintf(`%s/24`, ip))\n\t\t\t}\n\t\t\tif defaultGW.Routes(ip) {\n\t\t\t\ts.T().Skipf(\"test can't run on host with route %s and cluster IP %s\", defaultGW.String(), ip)\n\t\t\t}\n\t\t}\n\t\treturn map[string]any{\"also-proxy\": apiServers}\n\t})\n\n\ts.TelepresenceConnect(ctx, \"--context\", \"extra\")\n\n\texpectedLen := len(ips)\n\texpect := regexp.MustCompile(`Also Proxy\\s*:\\s*\\((\\d+) subnets\\)`)\n\ts.Eventually(func() bool {\n\t\tstdout, stderr, err := itest.Telepresence(ctx, \"status\")\n\t\tif err == nil {\n\t\t\tif m := expect.FindStringSubmatch(stdout); m != nil && m[1] == strconv.Itoa(expectedLen) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tclog.Infof(ctx, \"%q does not match %q to %d subnets\", stdout, expect, expectedLen)\n\t\t} else {\n\t\t\tclog.Errorf(ctx, \"%s: %v\", stderr, err)\n\t\t}\n\t\treturn false\n\t}, 30*time.Second, 3*time.Second, fmt.Sprintf(\"did not find %d also-proxied subnets\", expectedLen))\n\n\tstatus := itest.TelepresenceStatusOk(ctx)\n\trequire.Len(status.RootDaemon.AlsoProxy, expectedLen)\n\tfor _, ip := range ips {\n\t\trng := ip.As4()\n\t\trng[len(rng)-1] = 0\n\t\texpectedValue := netip.PrefixFrom(netip.AddrFrom4(rng), 24)\n\t\trequire.Contains(status.RootDaemon.AlsoProxy, expectedValue)\n\t}\n}\n\nfunc (s *notConnectedSuite) Test_NeverProxy() {\n\trequire := s.Require()\n\tctx := s.Context()\n\n\tsvcName := \"echo-never-proxy\"\n\titest.ApplyEchoService(ctx, svcName, s.AppNamespace(), 8080)\n\tdefer itest.DeleteSvcAndWorkload(ctx, \"deploy\", svcName, s.AppNamespace())\n\tip, err := itest.Output(ctx, \"kubectl\",\n\t\t\"--namespace\", s.AppNamespace(),\n\t\t\"get\", \"svc\", svcName,\n\t\t\"-o\",\n\t\t\"jsonpath={.spec.clusterIP}\")\n\trequire.NoError(err)\n\tmask := 32\n\tif s.IsIPv6() {\n\t\tmask = 128\n\t}\n\tneverProxiedCount := 1\n\tctx = itest.WithKubeConfigExtension(ctx, func(cluster *api.Cluster) map[string]any {\n\t\tips, err := getClusterIPs(cluster)\n\t\trequire.NoError(err)\n\t\tfor _, cip := range ips {\n\t\t\tif !cip.IsLoopback() {\n\t\t\t\tclog.Infof(ctx, \"expect never-proxy of %s\", cip)\n\t\t\t\tneverProxiedCount++\n\t\t\t}\n\t\t}\n\t\treturn map[string]any{\"never-proxy\": []string{fmt.Sprintf(\"%s/%d\", ip, mask)}}\n\t})\n\ts.TelepresenceConnect(ctx, \"--context\", \"extra\")\n\n\t// The cluster's IP address will be never proxied unless it's a loopback, so we gotta account for that.\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"status\")\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tm := regexp.MustCompile(`Never Proxy\\s*:\\s*\\((\\d+) subnets\\)`).FindStringSubmatch(stdout)\n\t\tnpcOk := false\n\t\tif m != nil {\n\t\t\tnpc, _ := strconv.Atoi(m[1])\n\t\t\tnpcOk = npc > 0 && npc <= neverProxiedCount\n\t\t}\n\t\tif !npcOk {\n\t\t\tclog.Infof(ctx, \"did not find 1-%d never-proxied subnets\\nOut: %s\", neverProxiedCount, stdout)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}, 5*time.Second, 1*time.Second, fmt.Sprintf(\"did not find 1-%d never-proxied subnets\", neverProxiedCount))\n\n\ts.Eventually(func() bool {\n\t\tstatus, err := itest.TelepresenceStatus(ctx)\n\t\tif err == nil && status.RootDaemon != nil {\n\t\t\tnpc := len(status.RootDaemon.NeverProxy)\n\t\t\treturn npc > 0 && npc <= neverProxiedCount\n\t\t}\n\t\treturn false\n\t}, 5*time.Second, 1*time.Second, fmt.Sprintf(\"did not find 1-%d never-proxied subnets in json status\", neverProxiedCount))\n\n\ts.Eventually(func() bool {\n\t\treturn itest.Run(ctx, \"curl\", \"--silent\", \"--max-time\", \"0.5\", ip) != nil\n\t}, 15*time.Second, 2*time.Second, fmt.Sprintf(\"never-proxied IP %s is reachable\", ip))\n}\n\nfunc (s *notConnectedSuite) Test_ConflictingProxies() {\n\tctx := s.Context()\n\n\ts.TelepresenceConnect(ctx)\n\tst := itest.TelepresenceStatusOk(ctx)\n\titest.TelepresenceQuitOk(ctx)\n\trq := s.Require()\n\trq.True(len(st.RootDaemon.Subnets) > 0)\n\tsvcCIDR := st.RootDaemon.Subnets[0]\n\tones := svcCIDR.Bits()\n\tif ones > 16 || !svcCIDR.Addr().Is4() {\n\t\ts.T().Skip(\"test requires an IPv4 service subnet with a 16 bit mask or smaller\")\n\t}\n\n\tbase := svcCIDR.Masked().Addr()\n\tlargeCIDR := netip.PrefixFrom(base, 24)\n\tsmallCIDR := netip.PrefixFrom(base, 28)\n\t// testIP is an IP that is covered by smallCIDR\n\tbaseBytes := base.As4()\n\ttestIP := netip.PrefixFrom(netip.AddrFrom4([4]byte{baseBytes[0], baseBytes[1], 0, 4}), 32)\n\t// We don't really care if we can't route this with TP disconnected provided the result is the same once we connect\n\toriginalRoute, _ := routing.GetRoute(ctx, testIP)\n\tfor name, t := range map[string]struct {\n\t\talsoProxy  []string\n\t\tneverProxy []string\n\t\texpectEq   bool\n\t}{\n\t\t\"Never Proxy wins\": {\n\t\t\talsoProxy:  []string{largeCIDR.String()},\n\t\t\tneverProxy: []string{smallCIDR.String()},\n\t\t\texpectEq:   true,\n\t\t},\n\t\t\"Also Proxy wins\": {\n\t\t\talsoProxy:  []string{smallCIDR.String()},\n\t\t\tneverProxy: []string{largeCIDR.String()},\n\t\t\texpectEq:   false,\n\t\t},\n\t} {\n\t\ts.Run(name, func() {\n\t\t\tctx := itest.WithKubeConfigExtension(s.Context(), func(cluster *api.Cluster) map[string]any {\n\t\t\t\treturn map[string]any{\n\t\t\t\t\t\"never-proxy\": t.neverProxy,\n\t\t\t\t\t\"also-proxy\":  t.alsoProxy,\n\t\t\t\t}\n\t\t\t})\n\t\t\ts.TelepresenceConnect(ctx, \"--context\", \"extra\")\n\t\t\tdefer itest.TelepresenceQuitOk(ctx)\n\t\t\ts.Eventually(func() bool {\n\t\t\t\tnewRoute, err := routing.GetRoute(ctx, testIP)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"failed to get route for %s: %v\", testIP, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif t.expectEq {\n\t\t\t\t\treturn originalRoute.InterfaceName == newRoute.InterfaceName\n\t\t\t\t}\n\t\t\t\treturn newRoute.InterfaceName != originalRoute.InterfaceName\n\t\t\t}, 30*time.Second, 200*time.Millisecond)\n\t\t})\n\t}\n}\n\nfunc (s *notConnectedSuite) Test_AlsoNeverProxyDocker() {\n\tif s.IsCI() && !(runtime.GOOS == \"linux\" && runtime.GOARCH == \"amd64\") {\n\t\ts.T().Skip(\"CI can't run linux docker containers inside non-linux runners\")\n\t}\n\talsoProxy := []string{\"10.128.0.0/16\"}\n\tneverProxy := []string{\"10.128.0.0/24\"}\n\tctx := itest.WithKubeConfigExtension(s.Context(), func(cluster *api.Cluster) map[string]any {\n\t\treturn map[string]any{\n\t\t\t\"never-proxy\": neverProxy,\n\t\t\t\"also-proxy\":  alsoProxy,\n\t\t}\n\t})\n\tcidrsToStrings := func(cidrs []netip.Prefix) []string {\n\t\tss := make([]string, len(cidrs))\n\t\tfor i, cidr := range cidrs {\n\t\t\tss[i] = cidr.String()\n\t\t}\n\t\treturn ss\n\t}\n\tctx = itest.WithConfig(ctx, func(config client.Config) {\n\t\tconfig.Intercept().UseFtp = false\n\t})\n\ts.TelepresenceConnect(ctx, \"--context\", \"extra\", \"--docker\")\n\tdefer itest.TelepresenceQuitOk(ctx)\n\tst := itest.TelepresenceStatusOk(ctx)\n\ts.True(slice.ContainsAll(cidrsToStrings(st.ContainerizedDaemon.AlsoProxy), alsoProxy))\n\ts.True(slice.ContainsAll(cidrsToStrings(st.ContainerizedDaemon.NeverProxy), neverProxy))\n}\n\nfunc (s *notConnectedSuite) Test_DNSSuffixRules() {\n\tif s.IsCI() && runtime.GOOS == \"linux\" && runtime.GOARCH == \"arm64\" {\n\t\ts.T().Skip(\"The DNS on the linux-arm64 GitHub runner is not configured correctly\")\n\t}\n\n\tdefaults := client.GetDefaultConfig().DNS()\n\n\tconst randomName = \"zwslkjsdf\"\n\tconst randomDomain = \".xnrqj\"\n\tconst randomDomain2 = \".pvdar\"\n\ttests := []struct {\n\t\tname                    string\n\t\tdomainName              string\n\t\tincludeSuffixes         []string\n\t\texcludeSuffixes         []string\n\t\tconfigIncludeSuffixes   []string\n\t\twantedLogEntry          []string\n\t\tmustHaveWanted          bool\n\t\texpectedIncludeSuffixes []string\n\t\texpectedExcludeSuffixes []string\n\t\tunwantedLogEntry        []string\n\t}{\n\t\t{\n\t\t\t\"default-exclude-com\",\n\t\t\trandomName + \".com\",\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t[]string{\n\t\t\t\t`Cluster DNS excluded by exclude-suffix \".com\" for name \"` + randomName + `.com\"`,\n\t\t\t},\n\t\t\tfalse,\n\t\t\tdefaults.IncludeSuffixes,\n\t\t\tdefaults.ExcludeSuffixes,\n\t\t\t[]string{\n\t\t\t\t`Lookup A \"` + randomName + `.com`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"default-exclude-random-domain\",\n\t\t\trandomName + randomDomain,\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t[]string{\n\t\t\t\t`Cluster DNS excluded for name \"` + randomName + randomDomain + `\". No inclusion rule was matched`,\n\t\t\t},\n\t\t\tfalse,\n\t\t\tdefaults.IncludeSuffixes,\n\t\t\tdefaults.ExcludeSuffixes,\n\t\t\t[]string{\n\t\t\t\t`Lookup A \"` + randomName + randomDomain + `.\"`,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"include-random-domain\",\n\t\t\trandomName + randomDomain,\n\t\t\t[]string{randomDomain},\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t[]string{\n\t\t\t\t`Cluster DNS included by include-suffix \"` + randomDomain + `\" for name \"` + randomName + randomDomain + `\"`,\n\t\t\t\t`Lookup A \"` + randomName + randomDomain + `.\"`,\n\t\t\t},\n\t\t\ttrue,\n\t\t\t[]string{randomDomain},\n\t\t\tdefaults.ExcludeSuffixes,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"include-random-domain-config\",\n\t\t\trandomName + randomDomain,\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t[]string{randomDomain},\n\t\t\t[]string{\n\t\t\t\t`Cluster DNS included by include-suffix \"` + randomDomain + `\" for name \"` + randomName + randomDomain + `\"`,\n\t\t\t\t`Lookup A \"` + randomName + randomDomain + `.\"`,\n\t\t\t},\n\t\t\ttrue,\n\t\t\t[]string{randomDomain},\n\t\t\tdefaults.ExcludeSuffixes,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"override-random-domain\",\n\t\t\trandomName + randomDomain,\n\t\t\t[]string{randomDomain},\n\t\t\tnil,\n\t\t\t[]string{randomDomain2},\n\t\t\t[]string{\n\t\t\t\t`Cluster DNS included by include-suffix \"` + randomDomain + `\" for name \"` + randomName + randomDomain + `\"`,\n\t\t\t\t`Lookup A \"` + randomName + randomDomain + `.\"`,\n\t\t\t},\n\t\t\ttrue,\n\t\t\t[]string{randomDomain},\n\t\t\tdefaults.ExcludeSuffixes,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"equally specific include overrides exclude\",\n\t\t\trandomName + \".org\",\n\t\t\t[]string{\".org\"},\n\t\t\tnil,\n\t\t\tnil,\n\t\t\t[]string{\n\t\t\t\t`Cluster DNS included by include-suffix \".org\" (overriding exclude-suffix \".org\") for name \"` + randomName + `.org\"`,\n\t\t\t\t`Lookup A \"` + randomName + `.org.\"`,\n\t\t\t},\n\t\t\ttrue,\n\t\t\t[]string{\".org\"},\n\t\t\tdefaults.ExcludeSuffixes,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"more specific include overrides exclude\",\n\t\t\trandomName + \".my-domain.org\",\n\t\t\t[]string{\".my-domain.org\"},\n\t\t\tdefaults.ExcludeSuffixes,\n\t\t\tnil,\n\t\t\t[]string{\n\t\t\t\t`Cluster DNS included by include-suffix \".my-domain.org\" (overriding exclude-suffix \".org\") for name \"` + randomName + `.my-domain.org\"`,\n\t\t\t\t`Lookup A \"` + randomName + `.my-domain.org.\"`,\n\t\t\t},\n\t\t\ttrue,\n\t\t\t[]string{\".my-domain.org\"},\n\t\t\tdefaults.ExcludeSuffixes,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"more specific exclude overrides include\",\n\t\t\trandomName + \".my-domain.org\",\n\t\t\t[]string{\".org\"},\n\t\t\t[]string{\".com\", \".my-domain.org\"},\n\t\t\tnil,\n\t\t\t[]string{\n\t\t\t\t`Cluster DNS excluded by exclude-suffix \".my-domain.org\" for name \"` + randomName + `.my-domain.org\"`,\n\t\t\t},\n\t\t\ttrue,\n\t\t\t[]string{\".org\"},\n\t\t\t[]string{\".com\", \".my-domain.org\"},\n\t\t\t[]string{\n\t\t\t\t`Lookup A \"` + randomName + `.my-domain.org.\"`,\n\t\t\t},\n\t\t},\n\t}\n\tlogFile := filepath.Join(filelocation.AppUserLogDir(s.Context()), \"daemon.log\")\n\n\tfor _, tt := range tests {\n\t\ts.Run(tt.name, func() {\n\t\t\tctx := s.Context()\n\t\t\tif len(tt.configIncludeSuffixes) > 0 {\n\t\t\t\tctx = itest.WithConfig(ctx, func(config client.Config) {\n\t\t\t\t\tconfig.DNS().IncludeSuffixes = tt.configIncludeSuffixes\n\t\t\t\t})\n\t\t\t}\n\t\t\tctx = itest.WithKubeConfigExtension(ctx, func(cluster *api.Cluster) map[string]any {\n\t\t\t\tm := map[string]any{\n\t\t\t\t\t\"logLevels\": map[string]string{\n\t\t\t\t\t\t\"rootDaemon\": \"trace\",\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tif len(tt.excludeSuffixes)+len(tt.includeSuffixes) > 0 {\n\t\t\t\t\tdns := map[string]any{}\n\t\t\t\t\tif len(tt.excludeSuffixes) > 0 {\n\t\t\t\t\t\tdns[\"excludeSuffixes\"] = tt.excludeSuffixes\n\t\t\t\t\t}\n\t\t\t\t\tif len(tt.includeSuffixes) > 0 {\n\t\t\t\t\t\tdns[\"includeSuffixes\"] = tt.includeSuffixes\n\t\t\t\t\t}\n\t\t\t\t\tm[\"dns\"] = dns\n\t\t\t\t}\n\t\t\t\treturn m\n\t\t\t})\n\t\t\trequire := s.Require()\n\n\t\t\ts.TelepresenceConnect(ctx, \"--context\", \"extra\")\n\t\t\tdefer itest.TelepresenceQuitOk(ctx)\n\n\t\t\t// Check that config view -c includes the includeSuffixes\n\t\t\tvar cfg client.SessionConfig\n\t\t\tstdout := itest.TelepresenceOk(ctx, \"config\", \"view\", \"--client-only\", \"--output\", \"json\")\n\t\t\trequire.NoError(json.Unmarshal([]byte(stdout), &cfg, false))\n\t\t\trequire.Equal(tt.expectedExcludeSuffixes, cfg.DNS().ExcludeSuffixes)\n\t\t\trequire.Equal(tt.expectedIncludeSuffixes, cfg.DNS().IncludeSuffixes)\n\n\t\t\trootLog, err := os.Open(logFile)\n\t\t\trequire.NoError(err)\n\t\t\tdefer rootLog.Close()\n\n\t\t\t// Figure out where the current end of the logfile is. This must be done before any\n\t\t\t// of the tests run because the queries that the DNS resolver receives are dependent\n\t\t\t// on how the system's DNS resolver handle search paths and caching.\n\t\t\tst, err := rootLog.Stat()\n\t\t\ts.Require().NoError(err)\n\t\t\tpos := st.Size()\n\n\t\t\tshort, cancel := context.WithTimeout(ctx, 20*time.Millisecond)\n\t\t\tdefer cancel()\n\t\t\t_, _ = net.DefaultResolver.LookupIPAddr(short, tt.domainName)\n\n\t\t\t// Give query time to reach telepresence and produce a log entry\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\n\t\t\tfor _, wl := range tt.wantedLogEntry {\n\t\t\t\t_, err = rootLog.Seek(pos, io.SeekStart)\n\t\t\t\trequire.NoError(err)\n\t\t\t\tscn := bufio.NewScanner(rootLog)\n\t\t\t\tfound := false\n\n\t\t\t\t// mustHaveWanted caters for cases where the default behavior from the system's resolver\n\t\t\t\t// is to not send unwanted queries to our resolver at all (based on search and routes).\n\t\t\t\t// It is forced to true for inclusion tests.\n\t\t\t\tmustHaveWanted := tt.mustHaveWanted\n\t\t\t\tfor scn.Scan() {\n\t\t\t\t\ttxt := scn.Text()\n\t\t\t\t\tif strings.Contains(txt, wl) {\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tif !mustHaveWanted {\n\t\t\t\t\t\tif strings.Contains(txt, \" ServeDNS \") && strings.Contains(txt, tt.domainName) {\n\t\t\t\t\t\t\tmustHaveWanted = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ts.Truef(found || !mustHaveWanted, \"Unable to find %q\", wl)\n\t\t\t}\n\n\t\t\tfor _, wl := range tt.unwantedLogEntry {\n\t\t\t\t_, err = rootLog.Seek(pos, io.SeekStart)\n\t\t\t\trequire.NoError(err)\n\t\t\t\tscn := bufio.NewScanner(rootLog)\n\t\t\t\tfound := false\n\t\t\t\tfor scn.Scan() {\n\t\t\t\t\tif strings.Contains(scn.Text(), wl) {\n\t\t\t\t\t\tfound = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ts.Falsef(found, \"Found unwanted %q\", wl)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "integration_test/large_files_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype largeFilesSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tname         string\n\tmanifests    [][3]itest.TplResource\n\tserviceCount int\n\tmountPoint   []string\n\tlargeFiles   []string\n}\n\nfunc (s *largeFilesSuite) SuiteName() string {\n\treturn \"LargeFiles\"\n}\n\nconst (\n\tsvcCount        = 4\n\tfileSize        = 100 * 1024 * 1024\n\tfileCountPerSvc = 3\n)\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &largeFilesSuite{\n\t\t\tSuite:          itest.Suite{Harness: h},\n\t\t\tTrafficManager: h,\n\t\t\tname:           \"hello\",\n\t\t\tserviceCount:   svcCount,\n\t\t\tmountPoint:     make([]string, svcCount),\n\t\t\tlargeFiles:     make([]string, svcCount*fileCountPerSvc),\n\t\t}\n\t})\n}\n\nfunc (s *largeFilesSuite) Name() string {\n\treturn s.name\n}\n\nfunc (s *largeFilesSuite) ServiceCount() int {\n\treturn s.serviceCount\n}\n\nfunc (s *largeFilesSuite) SetupSuite() {\n\tif !(s.ManagerIsVersion(\">2.21.x\") && s.ClientIsVersion(\">2.24.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility tests. Not enough transfer stability\")\n\t}\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\n\ts.manifests = make([][3]itest.TplResource, s.ServiceCount())\n\twg := sync.WaitGroup{}\n\twg.Add(s.ServiceCount())\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tpvName := fmt.Sprintf(\"%s-pv-%d\", s.Name(), i)\n\t\t\tpv := &itest.PersistentVolume{\n\t\t\t\tName: pvName,\n\t\t\t}\n\t\t\tif s.UseLocalPathProvisioner() {\n\t\t\t\tpv.Annotations = map[string]string{\n\t\t\t\t\t\"pv.kubernetes.io/provisioned-by\": \"rancher.io/local-path\",\n\t\t\t\t}\n\t\t\t\tpv.StorageClassName = \"local-path\"\n\t\t\t}\n\t\t\ts.NoError(pv.Apply(ctx, s.AppNamespace()))\n\n\t\t\tpvcName := fmt.Sprintf(\"%s-pvc-%d\", s.Name(), i)\n\t\t\tpvc := &itest.PersistentVolumeClaim{\n\t\t\t\tName: pvcName,\n\t\t\t}\n\t\t\tif s.UseLocalPathProvisioner() {\n\t\t\t\tpvc.Annotations = map[string]string{\n\t\t\t\t\t\"pv.kubernetes.io/provisioned-by\": \"rancher.io/local-path\",\n\t\t\t\t}\n\t\t\t\tpvc.StorageClassName = \"local-path\"\n\t\t\t}\n\t\t\ts.NoError(pvc.Apply(ctx, s.AppNamespace()))\n\n\t\t\tsvc := fmt.Sprintf(\"%s-%d\", s.Name(), i)\n\t\t\tdep := &itest.Generic{\n\t\t\t\tName:     svc,\n\t\t\t\tRegistry: \"ghcr.io/telepresenceio\",\n\t\t\t\tImage:    \"echo-server:latest\",\n\t\t\t\tVolumes: []core.Volume{\n\t\t\t\t\t{\n\t\t\t\t\t\tName: \"rw-volume\",\n\t\t\t\t\t\tVolumeSource: core.VolumeSource{\n\t\t\t\t\t\t\tPersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: pvc.Name},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tVolumeMounts: []core.VolumeMount{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:      \"rw-volume\",\n\t\t\t\t\t\tMountPath: \"/home/scratch\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\ts.NoError(dep.Apply(ctx, s.AppNamespace()))\n\t\t\ts.NoError(itest.RolloutStatusWait(ctx, s.AppNamespace(), \"deploy/\"+svc))\n\t\t\ts.manifests[i] = [3]itest.TplResource{dep, pvc, pv}\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc (s *largeFilesSuite) TearDownSuite() {\n\tctx := s.Context()\n\t// Delete in reverse order\n\twg := sync.WaitGroup{}\n\twg.Add(s.ServiceCount())\n\tfor _, trs := range s.manifests {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfor _, tr := range trs {\n\t\t\t\ts.NoError(tr.Delete(ctx))\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n\titest.TelepresenceQuitOk(ctx)\n}\n\nfunc (s *largeFilesSuite) createIntercepts(ctx context.Context) {\n\ts.TelepresenceConnect(ctx)\n\n\twg := sync.WaitGroup{}\n\twg.Add(s.ServiceCount())\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\tsvc := fmt.Sprintf(\"%s-%d\", s.Name(), i)\n\t\t\tstdout := itest.TelepresenceOk(ctx, \"intercept\",\n\t\t\t\t\"--detailed-output\",\n\t\t\t\t\"--output\", \"json\",\n\t\t\t\t\"--port\", strconv.Itoa(8080+i),\n\t\t\t\tsvc,\n\t\t\t)\n\t\t\tvar info intercept.Info\n\t\t\trequire := s.Require()\n\t\t\trequire.NoError(json.Unmarshal([]byte(stdout), &info))\n\t\t\trequire.Equal(svc, info.Name, ioutil.WriterToString(info.WriteTo))\n\t\t\trequire.NotNil(info.Mount)\n\t\t\ts.mountPoint[i] = info.Mount.LocalDir\n\t\t\ts.NoError(itest.RolloutStatusWait(ctx, s.AppNamespace(), \"deploy/\"+svc))\n\t\t\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\t\t}(i)\n\t}\n\twg.Wait()\n\ttime.Sleep(7 * time.Second)\n}\n\nfunc (s *largeFilesSuite) leaveIntercepts(ctx context.Context) {\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\titest.TelepresenceOk(ctx, \"leave\", fmt.Sprintf(\"%s-%d\", s.Name(), i))\n\t}\n}\n\nfunc (s *largeFilesSuite) Test_LargeFileIntercepts_fuseftp() {\n\tctx := itest.WithConfig(s.Context(), func(cfg client.Config) {\n\t\tcfg.Timeouts().PrivateFtpReadWrite = 2 * time.Minute\n\t\tcfg.Timeouts().PrivateFtpShutdown = 3 * time.Minute\n\t\tcfg.Intercept().UseFtp = true\n\t})\n\ts.largeFileIntercepts(ctx)\n}\n\nfunc (s *largeFilesSuite) Test_LargeFileIntercepts_sshfs() {\n\tctx := itest.WithConfig(s.Context(), func(cfg client.Config) {\n\t\tcfg.Intercept().UseFtp = false\n\t})\n\ts.largeFileIntercepts(ctx)\n}\n\nfunc (s *largeFilesSuite) largeFileIntercepts(ctx context.Context) {\n\ts.createIntercepts(ctx)\n\twg := sync.WaitGroup{}\n\n\t// Start by creating files in the mounted filesystem from entry 1 - fileCountPerSvc for each service.\n\t// We leave the first entry empty because in the next step, we want to create a file parallel to\n\t// validating the ones we create here so that there is heavy parallel reads and writes.\n\twg.Add(s.ServiceCount() * (fileCountPerSvc - 1))\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\tfor n := 1; n < fileCountPerSvc; n++ { // Leave the first entry empty for now\n\t\t\tgo func(i, n int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tpath, err := s.createLargeFile(filepath.Join(s.mountPoint[i], \"home\", \"scratch\"), fileSize)\n\t\t\t\ts.largeFiles[i*fileCountPerSvc+n] = filepath.Base(path)\n\t\t\t\ts.NoError(err)\n\t\t\t}(i, n)\n\t\t}\n\t}\n\twg.Wait()\n\n\t// At this point we leave the intercepts so that all directories are unmounted. The volumes are persistent\n\t// so they will be remounted.\n\ts.leaveIntercepts(ctx)\n\tif s.T().Failed() {\n\t\ts.T().FailNow()\n\t}\n\ts.createIntercepts(ctx)\n\n\t// Parallel to creating the first entry, also validate the ones that we created in step 1.\n\twg.Add(s.ServiceCount() * fileCountPerSvc)\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\tpath, err := s.createLargeFile(filepath.Join(s.mountPoint[i], \"home\", \"scratch\"), fileSize)\n\t\t\ts.largeFiles[i*fileCountPerSvc] = filepath.Base(path)\n\t\t\ts.NoError(err)\n\t\t}(i)\n\t\tfor n := 1; n < fileCountPerSvc; n++ { // Leave the first entry empty for now\n\t\t\tgo func(i, n int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\ts.NoError(itest.TimedRun(ctx, 10*time.Second, func(_ context.Context) error {\n\t\t\t\t\treturn validateLargeFile(filepath.Join(s.mountPoint[i], \"home\", \"scratch\", s.largeFiles[i*fileCountPerSvc+n]), fileSize)\n\t\t\t\t}))\n\t\t\t}(i, n)\n\t\t}\n\t}\n\twg.Wait()\n\ts.leaveIntercepts(ctx)\n\tif s.T().Failed() {\n\t\ts.T().FailNow()\n\t}\n\ts.createIntercepts(ctx)\n\tdefer s.leaveIntercepts(ctx)\n\n\t// Validate the first entry\n\twg.Add(s.ServiceCount())\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\ts.NoError(itest.TimedRun(ctx, 10*time.Second, func(_ context.Context) error {\n\t\t\t\treturn validateLargeFile(filepath.Join(s.mountPoint[i], \"home\", \"scratch\", s.largeFiles[i*fileCountPerSvc]), fileSize)\n\t\t\t}))\n\t\t}(i)\n\t}\n\twg.Wait()\n}\n\nfunc (s *largeFilesSuite) createLargeFile(dir string, sz int) (string, error) {\n\tif sz%4 != 0 {\n\t\treturn \"\", errors.New(\"size%4 must be zero\")\n\t}\n\tqsz := sz / 4 // We'll write a sequence of uint32 values\n\tif qsz > math.MaxUint32 {\n\t\treturn \"\", fmt.Errorf(\"size must be less than %d\", math.MaxUint32*4)\n\t}\n\tf, err := os.CreateTemp(dir, \"big-*.bin\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"%s: os.CreateTemp failed: %w\", time.Now().Format(\"15:04:05.0000\"), err)\n\t}\n\tdefer f.Close()\n\tbf := bufio.NewWriter(f)\n\n\tqz := uint32(qsz)\n\tbuf := make([]byte, 4)\n\tfor i := uint32(0); i < qz; i++ {\n\t\tbinary.BigEndian.PutUint32(buf, i)\n\t\tn, err := bf.Write(buf)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"%s: Write on %s failed: %w\", time.Now().Format(\"15:04:05.0000\"), f.Name(), err)\n\t\t}\n\t\tif n != 4 {\n\t\t\treturn \"\", errors.New(\"didn't write quartet\")\n\t\t}\n\t}\n\tif err := bf.Flush(); err != nil {\n\t\treturn \"\", fmt.Errorf(\"%s: Flush on %s failed: %w\", time.Now().Format(\"15:04:05.0000\"), f.Name(), err)\n\t}\n\treturn f.Name(), nil\n}\n\nfunc validateLargeFile(name string, sz int) error {\n\tf, err := os.Open(name)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\tst, err := f.Stat()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif st.Size() != int64(sz) {\n\t\treturn fmt.Errorf(\"file size differ. Expected %d, got %d\", sz, st.Size())\n\t}\n\tbf := bufio.NewReader(f)\n\tqz := uint32(sz / 4)\n\tbuf := make([]byte, 4)\n\tfor i := uint32(0); i < qz; i++ {\n\t\tn, err := bf.Read(buf)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif n != 4 {\n\t\t\treturn errors.New(\"didn't read quartet\")\n\t\t}\n\t\tx := binary.BigEndian.Uint32(buf)\n\t\tif i != x {\n\t\t\treturn fmt.Errorf(\"content differ at position %d: expected %d, got %d\", i*4, i, x)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "integration_test/limitrange_test.go",
    "content": "package integration_test\n\nimport (\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\nfunc (is *installSuite) limitedRangeTest() {\n\tconst svc = \"echo\"\n\tctx := itest.WithUser(is.Context(), is.ManagerNamespace()+\":\"+itest.TestUser)\n\tis.TelepresenceConnect(ctx)\n\titest.TelepresenceOk(ctx, \"loglevel\", \"debug\")\n\n\trequire := is.Require()\n\titest.ApplyEchoService(ctx, svc, is.AppNamespace(), 8083)\n\tdefer func() {\n\t\tis.NoError(itest.Kubectl(ctx, is.AppNamespace(), \"delete\", \"svc,deploy\", svc))\n\t\tis.Eventually(func() bool { return len(itest.RunningPodNames(ctx, svc, is.AppNamespace())) == 0 }, 2*time.Minute, 6*time.Second)\n\t}()\n\n\t_, _, err := itest.Telepresence(ctx, \"intercept\", \"--mount\", \"false\", svc)\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\tif out, err := itest.KubectlOut(ctx, is.AppNamespace(), \"get\", \"pod\", \"-o\", \"yaml\", \"-l\", \"app=\"+svc); err == nil {\n\t\t\tclog.Info(ctx, out)\n\t\t}\n\t}\n\trequire.NoError(err)\n\tis.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\t\treturn err == nil && strings.Contains(stdout, svc+\": intercepted\")\n\t\t},\n\t\t10*time.Second,\n\t\t2*time.Second,\n\t)\n\titest.TelepresenceOk(ctx, \"leave\", svc)\n\n\t// Ensure that LimitRange is injected into traffic-agent\n\tout, err := itest.KubectlOut(ctx, is.AppNamespace(), \"get\", \"pods\", \"-l\", \"app=\"+svc, \"-o\",\n\t\t`jsonpath={range .items.*.spec.containers[?(@.name=='traffic-agent')]}{.resources}{\",\"}{end}`)\n\trequire.NoError(err)\n\tclog.Infof(ctx, \"resources = %s\", out)\n\tvar rrs []v1.ResourceRequirements\n\trequire.NoError(json.Unmarshal([]byte(\"[\"+strings.TrimSuffix(out, \",\")+\"]\"), &rrs))\n\toneGig, err := resource.ParseQuantity(\"100Mi\")\n\trequire.NoError(err)\n\trequire.NotEmpty(rrs)\n\trr := rrs[0]\n\tm := rr.Limits.Memory()\n\trequire.True(m != nil && m.Equal(oneGig))\n\tm = rr.Requests.Memory()\n\trequire.True(m != nil && m.Equal(oneGig))\n}\n\nfunc (is *installSuite) TestLimitRange() {\n\tctx := is.Context()\n\trequire := is.Require()\n\trequire.NoError(itest.Kubectl(ctx, is.ManagerNamespace(), \"apply\", \"-f\", filepath.Join(\"testdata\", \"k8s\", \"client_sa.yaml\")))\n\tdefer func() {\n\t\trequire.NoError(itest.Kubectl(ctx, is.ManagerNamespace(), \"delete\", \"-f\", filepath.Join(\"testdata\", \"k8s\", \"client_sa.yaml\")))\n\t}()\n\n\tdefer is.UninstallTrafficManager(ctx, is.ManagerNamespace())\n\n\trequire.NoError(itest.Kubectl(ctx, is.AppNamespace(), \"apply\", \"-f\", filepath.Join(\"testdata\", \"k8s\", \"memory-constraints.yaml\")))\n\tdefer func() {\n\t\trequire.NoError(itest.Kubectl(ctx, is.AppNamespace(), \"delete\", \"-f\", filepath.Join(\"testdata\", \"k8s\", \"memory-constraints.yaml\")))\n\t}()\n\n\tis.Run(\"Never\", func() {\n\t\topts := []string{\"--set\", \"agentInjector.webhook.reinvocationPolicy=Never\"}\n\t\tif is.ManagerIsVersion(\">2.25.x\") {\n\t\t\topts = append(opts, \"--set\", \"agentInjector.mutationAware=false\")\n\t\t}\n\t\tis.TelepresenceHelmInstallOK(is.Context(), false, opts...)\n\t\tis.limitedRangeTest()\n\t})\n\n\tis.Run(\"IfNeeded\", func() {\n\t\topts := []string{\"--set\", \"agentInjector.webhook.reinvocationPolicy=IfNeeded\"}\n\t\tif is.ManagerIsVersion(\">2.25.x\") {\n\t\t\topts = append(opts, \"--set\", \"agentInjector.mutationAware=true\")\n\t\t}\n\t\tis.TelepresenceHelmInstallOK(is.Context(), true, opts...)\n\t\tis.limitedRangeTest()\n\t})\n}\n"
  },
  {
    "path": "integration_test/list_watch_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\nfunc (s *connectedSuite) Test_ListWatch() {\n\tsvc := \"echo-easy\"\n\n\ts.Run(\"<ctrl>-C\", func() {\n\t\t// Use a context to end tele list -w\n\t\tctx := s.Context()\n\t\tcancelctx, cancel := context.WithCancel(ctx)\n\t\tch := make(chan string)\n\t\tgo func() {\n\t\t\tstdout, _, _ := itest.Telepresence(cancelctx, \"list\", \"--output\", \"json-stream\")\n\t\t\tch <- stdout\n\t\t}()\n\t\ttime.Sleep(time.Second)\n\t\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\t\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\t\ttime.Sleep(time.Second)\n\t\tcancel()\n\t\ts.Contains(<-ch, svc)\n\t})\n}\n"
  },
  {
    "path": "integration_test/loglevel_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc (s *notConnectedSuite) Test_RootDaemonLogLevel() {\n\trequire := s.Require()\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\titest.TelepresenceQuitOk(ctx)\n\trootLogName := filepath.Join(filelocation.AppUserLogDir(ctx), \"daemon.log\")\n\trootLog, err := os.Open(rootLogName)\n\trequire.NoError(err)\n\tdefer rootLog.Close()\n\n\thasDebug := false\n\tscn := bufio.NewScanner(rootLog)\n\tmatch := regexp.MustCompile(` DEBUG +rootd/server`)\n\tfor scn.Scan() && !hasDebug {\n\t\thasDebug = match.MatchString(scn.Text())\n\t}\n\ts.True(hasDebug, \"daemon.log does not contain expected debug statements\")\n}\n"
  },
  {
    "path": "integration_test/manager_grpc_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\n\t\"google.golang.org/grpc\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/userd/trafficmgr\"\n)\n\ntype managerGRPCSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tconn *grpc.ClientConn\n\tsi   *manager.SessionInfo\n}\n\nfunc (m *managerGRPCSuite) SuiteName() string {\n\treturn \"ManagerGRPC\"\n}\n\nfunc init() {\n\titest.AddConnectedSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &managerGRPCSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (m *managerGRPCSuite) SetupSuite() {\n\tm.Suite.SetupSuite()\n\n\tk8sCluster, err := m.GetK8SCluster(m.Context(), \"\", m.ManagerNamespace())\n\tm.Require().NoError(err)\n\n\tm.conn, _, _, err = k8sCluster.ConnectToManager(m.Context(), m.ManagerNamespace())\n\tm.Require().NoError(err)\n\n\t_, err = manager.NewManagerClient(m.conn).Version(m.Context(), &empty.Empty{})\n\tm.Require().NoError(err)\n\n\tdaemonID := daemon.NewIdentifier(\"\", k8sCluster.KubeContext, m.AppNamespace(), false)\n\tm.si, err = trafficmgr.LoadSessionInfoFromUserCache(m.Context(), daemonID)\n\tm.Require().NoError(err)\n}\n\nfunc (m *managerGRPCSuite) TearDownSuite() {\n\tif m.conn != nil {\n\t\tgo m.conn.Close()\n\t\tm.conn = nil\n\t}\n}\n\nfunc (m *managerGRPCSuite) Test_ClusterInfo() {\n\tistream, err := manager.NewManagerClient(m.conn).WatchClusterInfo(m.Context(), m.si)\n\tm.Require().NoError(err)\n\tinfo, err := istream.Recv()\n\tm.Require().NoError(err)\n\t// We can't really legislate for the IPs, but we can make sure they're there. The rest should be the default config values.\n\tm.Require().NotNil(info.ManagerPodIp)\n\tm.Require().Equal(int32(8081), info.ManagerPodPort)\n\tm.Require().NotNil(info.InjectorSvcIp)\n\tinjectorSvcPort := int32(443)\n\tif m.ManagerIsVersion(\">=2.24.0\") {\n\t\tinjectorSvcPort = 8443\n\t}\n\tm.Require().Equal(injectorSvcPort, info.InjectorSvcPort)\n\tm.Require().Equal(fmt.Sprintf(\"agent-injector.%s\", m.ManagerNamespace()), info.InjectorSvcHost)\n}\n"
  },
  {
    "path": "integration_test/manual_agent_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nfunc (s *notConnectedSuite) Test_ManualAgent() {\n\ttestManualAgent(&s.Suite, s)\n}\n\nfunc testManualAgent(s *itest.Suite, nsp itest.NamespacePair) {\n\tif !(s.ManagerVersion().EQ(version.Structured) && s.ClientVersion().EQ(version.Structured)) {\n\t\ts.T().Skip(\"Not part of compatibility tests. Manual setup often change between versions\")\n\t}\n\trequire := s.Require()\n\tctx := s.Context()\n\n\tk8sDir := filepath.Join(\"testdata\", \"k8s\")\n\trequire.NoError(nsp.Kubectl(ctx, \"apply\", \"-f\", filepath.Join(k8sDir, \"echo-manual-inject-svc.yaml\")))\n\n\tagentImage := fmt.Sprintf(\"%s/%s:%s\", s.AgentRegistry(), s.AgentImage(), s.AgentVersion())\n\tinputFile := filepath.Join(k8sDir, \"echo-manual-inject-deploy.yaml\")\n\tcfgEntry := itest.TelepresenceOk(ctx, \"genyaml\", \"config\",\n\t\t\"--agent-image\", agentImage,\n\t\t\"--output\", \"-\",\n\t\t\"--manager-namespace\", nsp.ManagerNamespace(),\n\t\t\"--namespace\", nsp.AppNamespace(),\n\t\t\"--input\", inputFile,\n\t\t\"--loglevel\", \"debug\")\n\tsc, err := agentconfig.UnmarshalYAML([]byte(cfgEntry))\n\trequire.NoError(err)\n\n\ttmpDir := s.T().TempDir()\n\twriteYaml := func(name string, data any) string {\n\t\tyf := filepath.Join(tmpDir, name)\n\t\tb, err := yaml.Marshal(data)\n\t\trequire.NoError(err)\n\t\trequire.NoError(os.WriteFile(yf, b, 0o666))\n\t\treturn yf\n\t}\n\n\tconfigFile := filepath.Join(tmpDir, sc.WorkloadName)\n\trequire.NoError(os.WriteFile(configFile, []byte(cfgEntry), 0o666))\n\n\tstdout := itest.TelepresenceOk(ctx, \"genyaml\", \"container\",\n\t\t\"--output\", \"-\",\n\t\t\"--agent\", configFile,\n\t\t\"--input\", filepath.Join(k8sDir, \"echo-manual-inject-deploy.yaml\"))\n\tvar container map[string]any\n\trequire.NoError(yaml.Unmarshal([]byte(stdout), &container))\n\n\tstdout = itest.TelepresenceOk(ctx, \"genyaml\", \"initcontainer\", \"--output\", \"-\", \"--agent\", configFile)\n\tvar initContainer map[string]any\n\trequire.NoError(yaml.Unmarshal([]byte(stdout), &initContainer))\n\n\tstdout = itest.TelepresenceOk(ctx, \"genyaml\", \"volume\", \"--agent\", configFile, \"--input\", inputFile)\n\tvar volumes []map[string]any\n\trequire.NoError(yaml.Unmarshal([]byte(stdout), &volumes))\n\n\tstdout = itest.TelepresenceOk(ctx, \"genyaml\", \"annotations\", \"--agent\", configFile)\n\tvar anns map[string]string\n\trequire.NoError(yaml.Unmarshal([]byte(stdout), &anns))\n\n\tb, err := os.ReadFile(filepath.Join(k8sDir, \"echo-manual-inject-deploy.yaml\"))\n\trequire.NoError(err)\n\tvar deploy map[string]any\n\tb, err = yaml.YAMLToJSON(b)\n\trequire.NoError(err, string(b))\n\terr = json.Unmarshal(b, &deploy)\n\trequire.NoError(err)\n\n\trenameHttpPort := func(con map[string]any) {\n\t\tif ports, ok := con[\"ports\"].([]map[string]any); ok {\n\t\t\tfor _, port := range ports {\n\t\t\t\tif port[\"name\"] == \"http\" {\n\t\t\t\t\tport[\"name\"] = \"tm_http\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpodTemplate := deploy[\"spec\"].(map[string]any)[\"template\"].(map[string]any)\n\tpodSpec := podTemplate[\"spec\"].(map[string]any)\n\tcons := podSpec[\"containers\"].([]any)\n\tfor _, con := range cons {\n\t\trenameHttpPort(con.(map[string]any))\n\t}\n\tpodSpec[\"containers\"] = append(cons, container)\n\tpodSpec[\"initContainers\"] = []map[string]any{initContainer}\n\tpodSpec[\"volumes\"] = volumes\n\tpodTemplate[\"metadata\"].(map[string]any)[\"annotations\"] = anns\n\n\tdplYaml := writeYaml(\"deployment.yaml\", deploy)\n\trequire.NoError(nsp.Kubectl(ctx, \"apply\", \"-f\", dplYaml))\n\tdefer func() {\n\t\trequire.NoError(nsp.Kubectl(ctx, \"delete\", \"-f\", dplYaml))\n\t}()\n\n\terr = nsp.RolloutStatusWait(ctx, \"deploy/\"+sc.WorkloadName)\n\tnsp.CapturePodLogs(ctx, sc.WorkloadName, \"traffic-agent\", nsp.AppNamespace())\n\trequire.NoError(err)\n\n\tnsp.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceQuitOk(ctx)\n\n\tstdout = itest.TelepresenceOk(ctx, \"list\")\n\trequire.Regexp(regexp.MustCompile(`.*`+sc.WorkloadName+`\\s*:\\s*ready to (engage|intercept) \\(traffic-agent already installed\\).*`), stdout)\n\n\tsvcPort, svcCancel := itest.StartLocalHttpEchoServer(ctx, sc.WorkloadName)\n\tdefer svcCancel()\n\n\titest.TelepresenceOk(ctx, \"intercept\", sc.WorkloadName, \"--port\", strconv.Itoa(svcPort))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", sc.WorkloadName)\n\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && strings.Contains(stdout, sc.WorkloadName+\": intercepted\")\n\t}, 30*time.Second, 3*time.Second)\n\n\titest.PingInterceptedEchoServer(ctx, sc.WorkloadName, \"80\")\n}\n"
  },
  {
    "path": "integration_test/mounts_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype mountsSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *mountsSuite) SuiteName() string {\n\treturn \"Mounts\"\n}\n\nfunc init() {\n\titest.AddConnectedSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &mountsSuite{\n\t\t\tSuite:          itest.Suite{Harness: h},\n\t\t\tTrafficManager: h,\n\t\t}\n\t})\n}\n\nfunc (s *mountsSuite) SetupSuite() {\n\tif s.IsCI() && runtime.GOOS == \"darwin\" {\n\t\ts.T().Skip(\"Mount tests don't run on darwin due to macFUSE issues\")\n\t\treturn\n\t}\n\ts.Suite.SetupSuite()\n}\n\nfunc (s *mountsSuite) createDeployment() [3]itest.TplResource {\n\tctx := s.Context()\n\trq := s.Require()\n\n\tpv := &itest.PersistentVolume{\n\t\tName: \"local-pv\",\n\t}\n\tif s.UseLocalPathProvisioner() {\n\t\tpv.Annotations = map[string]string{\n\t\t\t\"pv.kubernetes.io/provisioned-by\": \"rancher.io/local-path\",\n\t\t}\n\t\tpv.StorageClassName = \"local-path\"\n\t}\n\trq.NoError(pv.Apply(ctx, s.AppNamespace()))\n\n\tpvc := &itest.PersistentVolumeClaim{\n\t\tName: \"local-pvc\",\n\t}\n\tif s.UseLocalPathProvisioner() {\n\t\tpvc.Annotations = map[string]string{\n\t\t\t\"pv.kubernetes.io/provisioned-by\": \"rancher.io/local-path\",\n\t\t}\n\t\tpvc.StorageClassName = \"local-path\"\n\t}\n\trq.NoError(pvc.Apply(ctx, s.AppNamespace()))\n\n\tdep := &itest.Generic{\n\t\tName:     \"hello\",\n\t\tRegistry: \"ghcr.io/telepresenceio\",\n\t\tImage:    \"echo-server:latest\",\n\t\tVolumes: []core.Volume{\n\t\t\t{\n\t\t\t\tName: \"rw-volume\",\n\t\t\t\tVolumeSource: core.VolumeSource{\n\t\t\t\t\tPersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: pvc.Name},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVolumeMounts: []core.VolumeMount{\n\t\t\t{\n\t\t\t\tName:      \"rw-volume\",\n\t\t\t\tMountPath: \"/data\",\n\t\t\t},\n\t\t},\n\t}\n\trq.NoError(dep.Apply(ctx, s.AppNamespace()))\n\trq.NoError(s.RolloutStatusWait(ctx, \"deployment/hello\"))\n\treturn [3]itest.TplResource{pv, pvc, dep}\n}\n\nfunc (s *mountsSuite) deleteDeployment(ts [3]itest.TplResource) {\n\t// Delete in reverse order\n\tfor i := 2; i >= 0; i-- {\n\t\ts.Require().NoError(ts[i].Delete(s.Context()))\n\t}\n}\n\nfunc (s *mountsSuite) Test_MountWrite() {\n\tif runtime.GOOS == \"windows\" {\n\t\ts.T().SkipNow()\n\t}\n\tts := s.createDeployment()\n\tdefer s.deleteDeployment(ts)\n\n\tctx := s.Context()\n\tmountPoint := filepath.Join(s.T().TempDir(), \"mnt\")\n\titest.TelepresenceOk(ctx, \"intercept\", \"hello\", \"--mount\", mountPoint, \"--port\", \"80:80\")\n\ttime.Sleep(2 * time.Second)\n\n\tcontent := \"hello world\\n\"\n\tpath := filepath.Join(mountPoint, \"data\", \"hello.txt\")\n\trq := s.Require()\n\trq.NoError(os.WriteFile(path, []byte(content), 0o644))\n\titest.TelepresenceOk(ctx, \"leave\", \"hello\")\n\ttime.Sleep(2 * time.Second)\n\n\tmountPoint = filepath.Join(s.T().TempDir(), \"data\")\n\titest.TelepresenceOk(ctx, \"intercept\", \"hello\", \"--mount\", mountPoint, \"--port\", \"80:80\")\n\tdefer itest.TelepresenceOk(ctx, \"leave\", \"hello\")\n\ts.CapturePodLogs(ctx, \"hello\", \"traffic-agent\", s.AppNamespace())\n\n\tpath = filepath.Join(mountPoint, \"data\", \"hello.txt\")\n\tdata, err := os.ReadFile(path)\n\trq.NoError(err)\n\trq.Equal(content, string(data))\n}\n\nfunc (s *mountsSuite) Test_MountReadOnly() {\n\tif runtime.GOOS == \"windows\" {\n\t\ts.T().SkipNow()\n\t}\n\trs := s.createDeployment()\n\tdefer s.deleteDeployment(rs)\n\tctx := s.Context()\n\n\tmountPoint := filepath.Join(s.T().TempDir(), \"mnt\")\n\titest.TelepresenceOk(ctx, \"intercept\", \"hello\", \"--mount\", mountPoint+\":ro\", \"--port\", \"80:80\")\n\tdefer itest.TelepresenceOk(ctx, \"leave\", \"hello\")\n\ttime.Sleep(2 * time.Second)\n\ts.Require().Error(os.WriteFile(filepath.Join(mountPoint, \"data\", \"hello.txt\"), []byte(\"hello world\\n\"), 0o644))\n}\n\n// Test_CollidingMounts tests that multiple mounts from several containers are managed correctly\n// by the traffic-agent and that an intercept of a container mounts the expected volumes.\nfunc (s *mountsSuite) Test_CollidingMounts() {\n\tctx := s.Context()\n\ts.ApplyTemplate(ctx, filepath.Join(\"testdata\", \"k8s\", \"hello-w-volumes.goyaml\"), nil)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", \"hello\")\n\n\ttype lm struct {\n\t\tname       string\n\t\tsvcPort    int\n\t\tmountPoint string\n\t}\n\tvar tests []lm\n\tif runtime.GOOS == \"windows\" {\n\t\ttests = []lm{\n\t\t\t{\n\t\t\t\t\"one\",\n\t\t\t\t80,\n\t\t\t\t\"O:\",\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"two\",\n\t\t\t\t81,\n\t\t\t\t\"T:\",\n\t\t\t},\n\t\t}\n\t} else {\n\t\ttempDir := s.T().TempDir()\n\t\ttests = []lm{\n\t\t\t{\n\t\t\t\t\"one\",\n\t\t\t\t80,\n\t\t\t\tfilepath.Join(tempDir, \"one\"),\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"two\",\n\t\t\t\t81,\n\t\t\t\tfilepath.Join(tempDir, \"two\"),\n\t\t\t},\n\t\t}\n\t}\n\n\tfor i, tt := range tests {\n\t\ts.Run(tt.name, func() {\n\t\t\tctx := s.Context()\n\t\t\trequire := s.Require()\n\t\t\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"hello\", \"--mount\", tt.mountPoint, \"--port\", fmt.Sprintf(\"%d:%d\", tt.svcPort, tt.svcPort))\n\t\t\tdefer itest.TelepresenceOk(ctx, \"leave\", \"hello\")\n\t\t\trequire.Contains(stdout, \"Using Deployment hello\")\n\t\t\tif i == 0 {\n\t\t\t\ts.CapturePodLogs(ctx, \"hello\", \"traffic-agent\", s.AppNamespace())\n\t\t\t} else {\n\t\t\t\t// Mounts are sometimes slow\n\t\t\t\ttime.Sleep(3 * time.Second)\n\t\t\t}\n\t\t\tns, err := os.ReadFile(filepath.Join(tt.mountPoint, \"var\", \"run\", \"secrets\", \"kubernetes.io\", \"serviceaccount\", \"namespace\"))\n\t\t\trequire.NoError(err)\n\t\t\trequire.Equal(s.AppNamespace(), string(ns))\n\t\t\ttoken, err := os.ReadFile(filepath.Join(tt.mountPoint, \"var\", \"run\", \"secrets\", \"kubernetes.io\", \"serviceaccount\", \"token\"))\n\t\t\trequire.NoError(err)\n\t\t\trequire.True(len(token) > 0)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "integration_test/multi_connect_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\tgoRuntime \"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/labels\"\n)\n\ntype multiConnectSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tappSpace2  string\n\tmgrSpace2  string\n\thandlerTag string\n}\n\nfunc (s *multiConnectSuite) SuiteName() string {\n\treturn \"MultiConnect\"\n}\n\nfunc init() {\n\t// This will give us one namespace pair with a traffic-manager installed.\n\titest.AddTrafficManagerSuite(\"-1\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &multiConnectSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *multiConnectSuite) SetupSuite() {\n\tif s.IsCI() && !(goRuntime.GOOS == \"linux\" && goRuntime.GOARCH == \"amd64\") {\n\t\ts.T().Skip(\"CI can't run linux docker containers inside non-linux runners\")\n\t}\n\ts.Suite.SetupSuite()\n\t// This will give us another namespace pair with a traffic-manager installed.\n\tctx := s.Context()\n\trequire := s.Require()\n\tsuffix := itest.GetGlobalHarness(s.HarnessContext()).Suffix()\n\ts.appSpace2, s.mgrSpace2 = itest.AppAndMgrNSName(suffix + \"-2\")\n\titest.CreateNamespaces(ctx, s.appSpace2, s.mgrSpace2)\n\n\tconst svc = \"echo\"\n\tappData := itest.AppData{\n\t\tAppName: svc,\n\t\tImage:   \"ghcr.io/telepresenceio/echo-server:latest\",\n\t\tPorts: []itest.AppPort{\n\t\t\t{\n\t\t\t\tServicePortNumber: 80,\n\t\t\t\tTargetPortName:    \"http\",\n\t\t\t\tTargetPortNumber:  8080,\n\t\t\t},\n\t\t},\n\t\tEnv: map[string]string{\"PORT\": \"8080\"},\n\t}\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\titest.ApplyAppTemplate(ctx, s.AppNamespace(), &appData)\n\t}()\n\tgo func() {\n\t\tdefer wg.Done()\n\t\titest.ApplyAppTemplate(ctx, s.appSpace2, &appData)\n\t}()\n\n\tctx2 := itest.WithNamespaces(ctx, &itest.Namespaces{Namespace: s.mgrSpace2, Selector: labels.SelectorFromNames(s.appSpace2)})\n\terr := itest.Kubectl(ctx2, s.mgrSpace2, \"apply\", \"-f\", filepath.Join(itest.GetOSSRoot(ctx2), \"testdata\", \"k8s\", \"client_sa.yaml\"))\n\trequire.NoError(err, \"failed to create connect ServiceAccount\")\n\tdb, err := itest.ReadTemplate(ctx, filepath.Join(\"testdata\", \"k8s\", \"client_rancher.goyaml\"), map[string]string{\n\t\t\"ManagerNamespace\": s.mgrSpace2,\n\t})\n\trequire.NoError(err)\n\trequire.NoError(err, itest.Kubectl(dos.WithStdin(ctx, bytes.NewReader(db)), s.mgrSpace2, \"apply\", \"-f\", \"-\"))\n\n\tctx2 = itest.WithUser(ctx2, s.mgrSpace2+\":\"+itest.TestUser)\n\ts.TelepresenceHelmInstallOK(ctx2, false)\n\titest.TelepresenceQuitOk(ctx2)\n\n\ts.handlerTag = \"telepresence/echo-test\"\n\ttestDir := \"testdata/echo-server\"\n\t_, err = itest.Output(ctx, \"docker\", \"build\", \"-t\", s.handlerTag, testDir)\n\trequire.NoError(err)\n\twg.Wait()\n\tif s.T().Failed() {\n\t\ts.T().FailNow()\n\t}\n}\n\nfunc (s *multiConnectSuite) AmendSuiteContext(ctx context.Context) context.Context {\n\treturn itest.WithConfig(ctx, func(cfg client.Config) {\n\t\tcfg.Intercept().UseFtp = false\n\t})\n}\n\nfunc (s *multiConnectSuite) TearDownSuite() {\n\tctx2 := itest.WithNamespaces(s.Context(), &itest.Namespaces{Namespace: s.mgrSpace2, Selector: labels.SelectorFromNames(s.appSpace2)})\n\ts.UninstallTrafficManager(ctx2, s.mgrSpace2)\n\titest.DeleteNamespaces(ctx2, s.appSpace2, s.mgrSpace2)\n}\n\nfunc (s *multiConnectSuite) Test_MultipleConnect() {\n\tctx := s.Context()\n\titest.TelepresenceOk(ctx, \"connect\", \"--docker\", \"--namespace\", s.AppNamespace(), \"--manager-namespace\", s.ManagerNamespace())\n\tdefer itest.TelepresenceDisconnectOk(ctx, \"--use\", s.AppNamespace())\n\tctx2 := itest.WithUser(ctx, s.mgrSpace2+\":\"+itest.TestUser)\n\titest.TelepresenceOk(ctx2, \"connect\", \"--docker\", \"--namespace\", s.appSpace2, \"--manager-namespace\", s.mgrSpace2)\n\tdefer itest.TelepresenceDisconnectOk(ctx2, \"--use\", s.appSpace2)\n\n\trequire := s.Require()\n\tkc := itest.KubeConfig(ctx)\n\tcfg, err := clientcmd.LoadFromFile(kc)\n\trequire.NoError(err)\n\tctxName := ioutil.SafeName(cfg.CurrentContext)\n\ts.doubleConnectCheck(ctx, ctx2, ctxName+\"-\"+s.AppNamespace()+\"-cn\", ctxName+\"-\"+s.appSpace2+\"-cn\", s.AppNamespace(), s.appSpace2, \"\")\n}\n\nfunc (s *multiConnectSuite) Test_MultipleConnect_named() {\n\tctx := s.Context()\n\tconst n1 = \"primary\"\n\tconst n2 = \"secondary\"\n\titest.TelepresenceOk(ctx, \"connect\", \"--docker\", \"--name\", n1, \"--namespace\", s.AppNamespace(), \"--manager-namespace\", s.ManagerNamespace())\n\tdefer itest.TelepresenceDisconnectOk(ctx, \"--use\", n1)\n\n\tctx2 := itest.WithUser(ctx, s.mgrSpace2+\":\"+itest.TestUser)\n\titest.TelepresenceOk(ctx2, \"connect\", \"--docker\", \"--name\", n2, \"--namespace\", s.appSpace2, \"--manager-namespace\", s.mgrSpace2)\n\tdefer itest.TelepresenceDisconnectOk(ctx2, \"--use\", n2)\n\n\ts.doubleConnectCheck(ctx, ctx2, n1, n2, s.AppNamespace(), s.appSpace2, \"\")\n}\n\n// Test_MultipleConnect_sameNamespace tests that we can have multiple connects to the same namespace when using\n// named connections. This can be of interest when ports on localhost in different intercepted pods collide but can't\n// be remapped because the intercept handler expects them to be specific numbers. Using multiple connections and\n// docker containers means that each container have its own localhost.\nfunc (s *multiConnectSuite) Test_MultipleConnect_sameNamespace() {\n\tctx := s.Context()\n\tconst n1 = \"first\"\n\tconst n2 = \"second\"\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--name\", n1)\n\tdefer itest.TelepresenceDisconnectOk(ctx, \"--use\", n1)\n\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--name\", n2)\n\tdefer itest.TelepresenceDisconnectOk(ctx, \"--use\", n2)\n\n\titest.ApplyEchoService(ctx, \"hello\", s.AppNamespace(), 80)\n\n\ts.doubleConnectCheck(ctx, ctx, n1, n2, s.AppNamespace(), s.AppNamespace(), \"hello\")\n}\n\nfunc (s *multiConnectSuite) doubleConnectCheck(ctx1, ctx2 context.Context, n1, n2, ns1, ns2, svc2 string) {\n\trequire := s.Require()\n\n\tst := itest.TelepresenceStatusOk(ctx1, \"--use\", n1)\n\trequire.Equal(st.UserDaemon.Namespace, ns1)\n\tname1 := st.UserDaemon.Name\n\n\tst = itest.TelepresenceStatusOk(ctx1, \"--use\", n2)\n\trequire.Equal(st.UserDaemon.Namespace, ns2)\n\tname2 := st.UserDaemon.Name\n\n\tcacheDir := filelocation.AppUserCacheDir(ctx1)\n\tvar di daemon.Info\n\n\tinfo, err := os.ReadFile(filepath.Join(cacheDir, \"userd\", n1+\".json\"))\n\trequire.NoError(err)\n\trequire.NoError(json.Unmarshal(info, &di))\n\trequire.Equal(ns1, di.Namespace)\n\n\tinfo, err = os.ReadFile(filepath.Join(cacheDir, \"userd\", n2+\".json\"))\n\trequire.NoError(err)\n\trequire.NoError(json.Unmarshal(info, &di))\n\trequire.Equal(ns2, di.Namespace)\n\n\tctx1, cancel1 := context.WithCancel(ctx1)\n\tdefer cancel1()\n\n\tctx2, cancel2 := context.WithCancel(ctx2)\n\tdefer cancel2()\n\n\twg := sync.WaitGroup{}\n\trunDockerRun := func(ctx context.Context, use, svc string, cancel context.CancelFunc) {\n\t\tdefer wg.Done()\n\t\tdefer cancel()\n\t\tso, se, err := itest.Telepresence(ctx, \"intercept\", \"--use\", use, \"--mount\", \"false\", svc, \"--docker-run\", \"--port\", \"8080\", \"--\", \"--rm\", \"--name\", use+\"-app\", s.handlerTag)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"intercept %s ended with error: %v\", svc, err)\n\t\t}\n\t\tif so != \"\" {\n\t\t\tclog.Infof(ctx, \"intercept %s stdout: %s\", svc, so)\n\t\t}\n\t\tif se != \"\" {\n\t\t\tclog.Infof(ctx, \"intercept %s stderr: %s\", svc, se)\n\t\t}\n\t}\n\n\tassertInterceptResponse := func(ctx context.Context, cn, svc string) {\n\t\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--use\", cn, \"--intercepts\")\n\t\t\tif err == nil {\n\t\t\t\tif strings.Contains(stdout, svc+\": intercepted\") {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tclog.Infof(ctx, \"stdout: %s\", stdout)\n\t\t\t} else {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t\treturn false\n\t\t}, 30*time.Second, 3*time.Second)\n\n\t\t// Response contains env variables TELEPRESENCE_CONTAINER and TELEPRESENCE_INTERCEPT_ID\n\t\texpectedOutput := regexp.MustCompile(`Intercept id [0-9a-f-]+:` + svc)\n\t\ts.Assert().EventuallyContext(ctx,\n\t\t\t// condition\n\t\t\tfunc() bool {\n\t\t\t\tot, et, err := itest.Telepresence(ctx, \"--use\", cn, \"curl\", \"--silent\", \"--max-time\", \"2\", svc)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"%s%s:%v\", ot, et, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tclog.Info(ctx, ot)\n\t\t\t\treturn expectedOutput.MatchString(ot)\n\t\t\t},\n\t\t\t20*time.Second, // waitFor\n\t\t\t3*time.Second,  // polling interval\n\t\t\t`body of %q matches %q`, \"http://\"+svc, expectedOutput,\n\t\t)\n\t}\n\n\tassertNotIntercepted := func(ctx context.Context, use, svc string) {\n\t\ts.Assert().EventuallyContext(ctx, func() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--use\", use, \"--intercepts\")\n\t\t\treturn err == nil && !strings.Contains(stdout, svc+\": intercepted\")\n\t\t}, 10*time.Second, 2*time.Second)\n\t}\n\n\tsvc1 := \"echo\"\n\tif svc2 == \"\" {\n\t\tsvc2 = svc1\n\t}\n\twg.Add(2)\n\tixCtx1, ixCancel1 := context.WithCancel(ctx1)\n\tdefer ixCancel1()\n\tgo runDockerRun(ctx1, n1, svc1, ixCancel1)\n\n\tixCtx2, ixCancel2 := context.WithCancel(ctx2)\n\tdefer ixCancel2()\n\tgo runDockerRun(ctx2, n2, svc2, ixCancel2)\n\n\tassertInterceptResponse(ixCtx1, name1, svc1)\n\tassertInterceptResponse(ixCtx2, name2, svc2)\n\n\titest.TelepresenceOk(ctx1, \"leave\", \"--use\", n1, svc1)\n\tassertNotIntercepted(ctx1, n1, svc1)\n\n\t// Other connection's intercept is still alive and kicking.\n\tassertInterceptResponse(ixCtx2, name2, svc2)\n\n\titest.TelepresenceOk(ctx2, \"leave\", \"--use\", n2, svc2)\n\tassertNotIntercepted(ctx2, n2, svc2)\n\n\tcancel1()\n\tcancel2()\n\twg.Wait()\n}\n"
  },
  {
    "path": "integration_test/multiple_intercepts_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype multipleInterceptsSuite struct {\n\titest.Suite\n\titest.MultipleServices\n\tservicePort   []int\n\tserviceCancel []context.CancelFunc\n}\n\nfunc (s *multipleInterceptsSuite) SuiteName() string {\n\treturn \"MultipleIntercepts\"\n}\n\nfunc init() {\n\titest.AddMultipleServicesSuite(\"\", \"hello\", 3, func(h itest.MultipleServices) itest.TestingSuite {\n\t\treturn &multipleInterceptsSuite{\n\t\t\tSuite:            itest.Suite{Harness: h},\n\t\t\tMultipleServices: h,\n\t\t\tservicePort:      make([]int, h.ServiceCount()),\n\t\t\tserviceCancel:    make([]context.CancelFunc, h.ServiceCount()),\n\t\t}\n\t})\n}\n\nfunc (s *multipleInterceptsSuite) SetupSuite() {\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\ts.servicePort[i], s.serviceCancel[i] = itest.StartLocalHttpEchoServer(ctx, fmt.Sprintf(\"%s-%d\", s.Name(), i))\n\t}\n\n\twg := sync.WaitGroup{}\n\twg.Add(s.ServiceCount())\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\tsvc := fmt.Sprintf(\"%s-%d\", s.Name(), i)\n\t\t\tstdout := itest.TelepresenceOk(ctx, \"intercept\", svc, \"--mount\", \"false\", \"--port\", strconv.Itoa(s.servicePort[i]))\n\t\t\ts.Contains(stdout, \"Using Deployment \"+svc)\n\t\t\ts.NoError(s.RolloutStatusWait(ctx, \"deploy/\"+svc))\n\t\t}(i)\n\t}\n\twg.Wait()\n}\n\nfunc (s *multipleInterceptsSuite) TearDownSuite() {\n\tctx := s.Context()\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\titest.TelepresenceOk(ctx, \"leave\", fmt.Sprintf(\"%s-%d\", s.Name(), i))\n\t}\n\tfor _, cancel := range s.serviceCancel {\n\t\tif cancel != nil {\n\t\t\tcancel()\n\t\t}\n\t}\n\t// Ensure that we have OK statuses on our services after leaving the intercept.\n\ts.Eventually(func() bool {\n\t\tstdout := itest.TelepresenceOk(ctx, \"list\", \"-n\", s.AppNamespace())\n\t\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\t\trx := regexp.MustCompile(fmt.Sprintf(`%s-%d\\s*: ready to (engage|intercept)`, s.Name(), i))\n\t\t\tif !rx.MatchString(stdout) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}, 30*time.Second, 2*time.Second)\n}\n\nfunc (s *multipleInterceptsSuite) Test_Intercepts() {\n\tctx := s.Context()\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\t\tif !regexp.MustCompile(fmt.Sprintf(`%s-%d\\s*: intercepted`, s.Name(), i)).MatchString(stdout) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}, 10*time.Second, time.Second)\n\n\twg := sync.WaitGroup{}\n\twg.Add(s.ServiceCount())\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\tgo func(i int) {\n\t\t\tdefer wg.Done()\n\t\t\tsvc := fmt.Sprintf(\"%s-%d\", s.Name(), i)\n\t\t\texpectedOutput := fmt.Sprintf(\"%s from intercept at /\", svc)\n\t\t\ts.Require().Eventually(\n\t\t\t\t// condition\n\t\t\t\tfunc() bool {\n\t\t\t\t\tip, err := net.DefaultResolver.LookupHost(ctx, svc)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tclog.Infof(ctx, \"%v\", err)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tif len(ip) != 1 {\n\t\t\t\t\t\tclog.Infof(ctx, \"Lookup for %s returned %v\", svc, ip)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\n\t\t\t\t\tclog.Infof(ctx, \"trying %q...\", \"http://\"+svc)\n\t\t\t\t\thc := http.Client{Timeout: 2 * time.Second}\n\t\t\t\t\tresp, err := hc.Get(\"http://\" + svc)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tclog.Infof(ctx, \"%v\", err)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\tclog.Infof(ctx, \"status code: %v\", resp.StatusCode)\n\t\t\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tclog.Infof(ctx, \"%v\", err)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tclog.Infof(ctx, \"body: %q\", body)\n\t\t\t\t\treturn string(body) == expectedOutput\n\t\t\t\t},\n\t\t\t\ttime.Minute,   // waitFor\n\t\t\t\t3*time.Second, // polling interval\n\t\t\t\t`body of %q equals %q`, \"http://\"+svc, expectedOutput,\n\t\t\t)\n\t\t}(i)\n\t}\n\twg.Wait()\n}\n\nfunc (s *multipleInterceptsSuite) Test_ReportsPortConflict() {\n\tctx := s.Context()\n\tsvc := fmt.Sprintf(\"%s-%d\", s.Name(), 0)\n\titest.TelepresenceOk(ctx, \"leave\", svc)\n\tdefer itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", \"--port\", strconv.Itoa(s.servicePort[0]), svc)\n\t_, stderr, err := itest.Telepresence(s.Context(), \"intercept\", \"--mount\", \"false\", \"--port\", strconv.Itoa(s.servicePort[1]), svc)\n\ts.Error(err)\n\ts.Contains(stderr, fmt.Sprintf(\"port 127.0.0.1:%d is already in use by intercept %s-1\", s.servicePort[1], s.Name()))\n}\n"
  },
  {
    "path": "integration_test/multiple_port_intercept_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype multiportInterceptSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tservicePort   [4]int\n\tserviceCancel [4]context.CancelFunc\n\tworkloads     [2]string\n\tports         [2][2]string\n}\n\nfunc (s *multiportInterceptSuite) SuiteName() string {\n\treturn \"MultiPortIntercept\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &multiportInterceptSuite{\n\t\t\tSuite:          itest.Suite{Harness: h},\n\t\t\tTrafficManager: h,\n\t\t\tworkloads:      [2]string{\"echo-both\", \"echo-double-one-unnamed\"},\n\t\t\tports:          [2][2]string{{\"one\", \"two\"}, {\"8080\", \"8081\"}},\n\t\t}\n\t})\n}\n\nfunc (s *multiportInterceptSuite) SetupSuite() {\n\tif !(s.ManagerIsVersion(\">2.21.x\") && s.ClientIsVersion(\">2.21.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility tests. Support for multiport intercept was introduced in 2.22\")\n\t}\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\tfor i := 0; i < 4; i++ {\n\t\ts.servicePort[i], s.serviceCancel[i] = itest.StartLocalHttpEchoServer(ctx, fmt.Sprintf(\"%s-%d\", \"echo\", i))\n\t}\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\tfor i := 0; i < 2; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tdep := s.workloads[i]\n\t\t\ts.ApplyApp(ctx, dep, \"deploy/\"+dep)\n\t\t}()\n\t}\n\ts.TelepresenceConnect(ctx)\n\twg.Wait()\n}\n\nfunc (s *multiportInterceptSuite) TearDownSuite() {\n\tctx := s.Context()\n\titest.TelepresenceQuitOk(ctx)\n\tfor i := 0; i < 2; i++ {\n\t\ts.DeleteApp(ctx, s.workloads[i])\n\t}\n\tfor i := 0; i < 4; i++ {\n\t\ts.serviceCancel[i]()\n\t}\n}\n\nfunc (s *multiportInterceptSuite) Test_MultiPortIntercept() {\n\tctx := s.Context()\n\tfor i := 0; i < 2; i++ {\n\t\titest.TelepresenceOk(ctx, \"intercept\", s.workloads[i],\n\t\t\t\"--mount=false\",\n\t\t\t\"--port\", fmt.Sprintf(\"%d:%s\", s.servicePort[i*2], s.ports[i][0]),\n\t\t\t\"--port\", fmt.Sprintf(\"%d:%s\", s.servicePort[i*2+1], s.ports[i][1]))\n\t}\n\tdefer func() {\n\t\tfor i := 0; i < 2; i++ {\n\t\t\titest.TelepresenceOk(ctx, \"leave\", s.workloads[i])\n\t\t}\n\t}()\n\n\trq := s.Require()\n\trq.Eventually(func() bool {\n\t\tst := itest.TelepresenceStatusOk(ctx)\n\t\tics := st.UserDaemon.Intercepts\n\t\tif len(ics) != 2 {\n\t\t\treturn false\n\t\t}\n\t\tfor i := 0; i < 2; i++ {\n\t\t\tic := ics[i]\n\t\t\tif ic.Name != s.workloads[i] {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}, 10*time.Second, time.Second)\n\n\tvar edoPods []core.Pod\n\trq.Eventually(func() bool {\n\t\tedoPods = itest.RunningPods(ctx, s.workloads[1], s.AppNamespace())\n\t\treturn len(edoPods) == 1\n\t}, 30*time.Second, 3*time.Second)\n\n\tedoPodIP := edoPods[0].Status.PodIP\n\tports := [4]string{\"80\", \"80\", \"8080\", \"8081\"}\n\tfor i, svc := range [4]string{\"echo-one\", \"echo-two\", edoPodIP, edoPodIP} {\n\t\titest.PingInterceptedEchoServer(ctx, fmt.Sprintf(\"%s/echo-%d\", svc, i), ports[i])\n\t}\n}\n\nfunc (s *multiportInterceptSuite) Test_MultiPortLocalConflict() {\n\tctx := s.Context()\n\titest.TelepresenceOk(ctx, \"intercept\", s.workloads[0],\n\t\t\"--mount=false\",\n\t\t\"--port\", fmt.Sprintf(\"%d:%s\", s.servicePort[0], s.ports[0][0]),\n\t\t\"--port\", fmt.Sprintf(\"%d:%s\", s.servicePort[1], s.ports[0][1]))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", s.workloads[0])\n\n\t_, _, err := itest.Telepresence(ctx, \"intercept\", s.workloads[1],\n\t\t\"--mount=false\",\n\t\t\"--port\", fmt.Sprintf(\"%d:%s\", s.servicePort[2], s.ports[1][0]),\n\t\t\"--port\", fmt.Sprintf(\"%d:%s\", s.servicePort[1], s.ports[1][1]))\n\ts.Require().Error(err)\n\ts.Contains(err.Error(), fmt.Sprintf(\"%d is already in use by intercept %s\", s.servicePort[1], s.workloads[0]))\n}\n\nfunc (s *multiportInterceptSuite) Test_MultiPortRemoteConflict() {\n\tctx := s.Context()\n\titest.TelepresenceOk(ctx, \"intercept\", s.workloads[0],\n\t\t\"--mount=false\",\n\t\t\"--port\", fmt.Sprintf(\"%d:%s\", s.servicePort[0], s.ports[0][0]),\n\t\t\"--port\", fmt.Sprintf(\"%d:%s\", s.servicePort[1], s.ports[0][1]))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", s.workloads[0])\n\n\t_, _, err := itest.Telepresence(ctx, \"intercept\", \"--workload\", s.workloads[0], s.workloads[0]+\"-again\",\n\t\t\"--mount=false\",\n\t\t\"--port\", strconv.Itoa(s.servicePort[1]), \"--service\", \"echo-two\")\n\ts.Require().Error(err)\n\ts.Regexp(`one intercept has no filters \\(intercepts all traffic\\)`, err.Error())\n}\n"
  },
  {
    "path": "integration_test/multiple_services_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype multipleServicesSuite struct {\n\titest.Suite\n\titest.MultipleServices\n}\n\nfunc (s *multipleServicesSuite) SuiteName() string {\n\treturn \"MultipleServices\"\n}\n\nfunc init() {\n\titest.AddMultipleServicesSuite(\"\", \"hello\", 3, func(h itest.MultipleServices) itest.TestingSuite {\n\t\treturn &multipleServicesSuite{Suite: itest.Suite{Harness: h}, MultipleServices: h}\n\t})\n}\n\nfunc (s *multipleServicesSuite) Test_LargeRequest() {\n\tif !(s.ManagerIsVersion(\">2.21.x\") && s.ClientIsVersion(\">2.21.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility tests. TUN-device isn't stable enough in versions <2.22.0\")\n\t}\n\tctx := s.Context()\n\titest.TelepresenceQuitOk(ctx)\n\ts.TelepresenceConnect(ctx)\n\tdefer func() {\n\t\t// Restore the connection to what it was before the test.\n\t\titest.TelepresenceQuitOk(ctx)\n\t\ts.TelepresenceConnect(s.Context())\n\t}()\n\n\tconst sendSize = 1024 * 1024 * 12\n\tconst varyMax = 1024 * 1024 * 4 // vary last 4Mi\n\tconst concurrentRequests = 64\n\n\ttb := [sendSize + varyMax]byte{}\n\ttb[0] = '!'\n\ttb[1] = '\\n'\n\tfor i := 2; i < sendSize+varyMax; i++ {\n\t\ttb[i] = 'A'\n\t}\n\n\ttime.Sleep(3 * time.Second)\n\twg := sync.WaitGroup{}\n\twg.Add(concurrentRequests)\n\tpingPong := func(x int) {\n\t\tdefer wg.Done()\n\t\tsendSize := sendSize + rand.Int()%varyMax // vary the last 64Ki to get random buffer sizes\n\t\tb := tb[:sendSize]\n\n\t\t// Distribute the requests over all services\n\t\turl := fmt.Sprintf(\"http://%s-%d.%s/put\", s.Name(), x%s.ServiceCount(), s.AppNamespace())\n\t\treq, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(b))\n\t\treq.ContentLength = int64(len(b))\n\t\tif !s.NoError(err) {\n\t\t\treturn\n\t\t}\n\n\t\tclient := &http.Client{Timeout: 60 * time.Second}\n\t\tresp, err := client.Do(req)\n\t\tif !s.NoError(err) {\n\t\t\treturn\n\t\t}\n\t\tbdy := resp.Body\n\t\tdefer bdy.Close()\n\t\tif !s.Equal(resp.StatusCode, 200) {\n\t\t\treturn\n\t\t}\n\n\t\tcl := sendSize + 1024\n\t\tbuf := make([]byte, cl)\n\t\ti := 0\n\t\tfor i < cl && err == nil {\n\t\t\tvar j int\n\t\t\tj, err = bdy.Read(buf[i:])\n\t\t\ti += j\n\t\t}\n\t\tif errors.Is(err, io.EOF) {\n\t\t\terr = nil\n\t\t}\n\t\tif s.NoError(err) {\n\t\t\tei := bytes.Index(buf, []byte{'!', '\\n'})\n\t\t\ts.GreaterOrEqual(ei, 0)\n\t\t\t// Do this instead of require.Equal(b, buf[ei:i]) so that on failure we don't print two very large buffers to the terminal\n\t\t\ts.Equal(true, bytes.Equal(b, buf[ei:i]))\n\t\t}\n\t}\n\tfor i := 0; i < concurrentRequests; i += 4 {\n\t\tgo func() {\n\t\t\tpingPong(i)\n\t\t\tpingPong(i + 1)\n\t\t\tpingPong(i + 2)\n\t\t\tpingPong(i + 3)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc (s *multipleServicesSuite) Test_List() {\n\tstdout := itest.TelepresenceOk(s.Context(), \"list\", \"-n\", s.AppNamespace())\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\ts.Regexp(fmt.Sprintf(`%s-%d\\s*: ready to (engage|intercept)`, s.Name(), i), stdout)\n\t}\n}\n\nfunc (s *multipleServicesSuite) Test_ListOnlyMapped() {\n\tctx := itest.WithUser(s.Context(), \"default\")\n\trequire := s.Require()\n\titest.TelepresenceDisconnectOk(ctx)\n\tdefer func() {\n\t\tctx := s.Context()\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\titest.TelepresenceOk(s.Context(), \"connect\", \"--namespace\", s.AppNamespace(), \"--manager-namespace\", s.ManagerNamespace())\n\t}()\n\ts.TelepresenceConnect(ctx, \"--mapped-namespaces\", \"default\")\n\n\tstdout := itest.TelepresenceOk(ctx, \"list\")\n\trequire.Contains(stdout, \"No Workloads\")\n\n\ts.TelepresenceConnect(ctx, \"--mapped-namespaces\", \"all\")\n\n\tstdout = itest.TelepresenceOk(ctx, \"list\")\n\trequire.NotContains(stdout, \"No Workloads\")\n}\n\nfunc (s *multipleServicesSuite) Test_RepeatedConnect() {\n\ttotalErrCount := int64(0)\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\turl := fmt.Sprintf(\"http://%s-%d.%s\", s.Name(), i, s.AppNamespace())\n\t\tfor v := 0; v < 30; v++ {\n\t\t\ts.Run(fmt.Sprintf(\"test-%d\", i*30+v), func() {\n\t\t\t\tctx := s.Context()\n\t\t\t\ts.T().Parallel()\n\t\t\t\ttime.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)\n\t\t\t\tassert := s.Assert()\n\t\t\t\tcl := http.Client{}\n\t\t\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\t\t\t\treq.Close = true\n\t\t\t\tassert.NoError(err)\n\t\t\t\tres, err := cl.Do(req)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif atomic.AddInt64(&totalErrCount, 1) > 2 {\n\t\t\t\t\t\ts.Failf(\"failed more than 2 times: %v\", err.Error())\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tassert.Equal(res.StatusCode, http.StatusOK)\n\t\t\t\t_, err = io.Copy(io.Discard, res.Body)\n\t\t\t\tassert.NoError(err)\n\t\t\t\tassert.NoError(res.Body.Close())\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc (s *multipleServicesSuite) Test_ProxiesOutboundTraffic() {\n\tctx := s.Context()\n\tfor i := 0; i < s.ServiceCount(); i++ {\n\t\tsvc := fmt.Sprintf(\"%s-%d.%s\", s.Name(), i, s.AppNamespace())\n\t\texpectedOutput := fmt.Sprintf(\"Request served by %s-%d\", s.Name(), i)\n\t\ts.Require().Eventually(\n\t\t\t// condition\n\t\t\tfunc() bool {\n\t\t\t\tclog.Infof(ctx, \"trying %q...\", \"http://\"+svc)\n\t\t\t\thc := http.Client{Timeout: time.Second}\n\t\t\t\tresp, err := hc.Get(\"http://\" + svc)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tdefer resp.Body.Close()\n\t\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tclog.Infof(ctx, \"body: %q\", body)\n\t\t\t\treturn strings.Contains(string(body), expectedOutput)\n\t\t\t},\n\t\t\t15*time.Second, // waitfor\n\t\t\t3*time.Second,  // polling interval\n\t\t\t`body of %q contains %q`, \"http://\"+svc, expectedOutput,\n\t\t)\n\t}\n}\n"
  },
  {
    "path": "integration_test/multiport_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\n// Test_MultipleUnnamedServicePorts tests the use-case where multiple services using\n// unnamed ports targets different container ports in the same workload and the same\n// container.\nfunc (s *connectedSuite) Test_MultipleUnnamedServicePorts() {\n\tif !s.ManagerIsVersion(\">2.21.x\") {\n\t\ts.T().Skip(`Not part of compatibility tests. No support for port annotation \"all\" in Versions < 2.22.0`)\n\t}\n\tctx := s.Context()\n\tdep := \"echo-double-one-unnamed\"\n\ts.ApplyApp(ctx, dep, \"deploy/\"+dep)\n\tdefer s.KubectlOk(ctx, \"delete\", \"deploy\", dep)\n\n\trequire := s.Require()\n\trequire.NoError(s.Kubectl(ctx, \"expose\", \"deploy\", dep, \"--port\", \"80\", \"--target-port\", \"8080\", \"--name\", dep+\"-\"+\"80\"))\n\tdefer s.KubectlOk(ctx, \"delete\", \"svc\", dep+\"-\"+\"80\")\n\trequire.NoError(s.Kubectl(ctx, \"expose\", \"deploy\", dep, \"--port\", \"81\", \"--target-port\", \"8081\", \"--name\", dep+\"-\"+\"81\"))\n\tdefer s.KubectlOk(ctx, \"delete\", \"svc\", dep+\"-\"+\"81\")\n\trequire.NoError(s.Kubectl(ctx, \"expose\", \"deploy\", dep, \"--port\", \"82\", \"--target-port\", \"8082\", \"--name\", dep+\"-\"+\"82\"))\n\tdefer s.KubectlOk(ctx, \"delete\", \"svc\", dep+\"-\"+\"82\")\n\n\tportTest := func(svcPort, targetPort string) {\n\t\tctx := s.Context()\n\t\tsvc := dep + \"-\" + svcPort\n\t\tlocalPort, cancel := itest.StartLocalHttpEchoServer(ctx, svc)\n\t\tdefer cancel()\n\t\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", \"-p\", fmt.Sprintf(\"%d:%s\", localPort, svcPort), dep)\n\t\tdefer itest.TelepresenceOk(ctx, \"leave\", dep)\n\t\titest.PingInterceptedEchoServer(ctx, svc, svcPort)\n\t}\n\ts.Run(\"port 80\", func() {\n\t\tportTest(\"80\", \"8080\")\n\t})\n\ts.Run(\"port 81\", func() {\n\t\tportTest(\"81\", \"8081\")\n\t})\n\ts.Run(\"port 82\", func() {\n\t\t// Must fail. The container doesn't expose port 8082\n\t\tctx := s.Context()\n\t\trequire := s.Require()\n\t\tsvcPort := \"82\"\n\t\tsvc := dep + \"-\" + svcPort\n\n\t\tlocalPort, cancel := itest.StartLocalHttpEchoServer(ctx, svc)\n\t\tdefer cancel()\n\t\t_, _, err := itest.Telepresence(ctx, \"intercept\", \"-p\", fmt.Sprintf(\"%d:%s\", localPort, svcPort), dep)\n\t\trequire.Error(err)\n\t})\n}\n\n// Test_MultipleUnnamedServicePorts tests the use-case where multiple services using\n// unnamed ports targets different container ports in the same workload and the same\n// container.\nfunc (s *connectedSuite) Test_NoContainerPort() {\n\tctx := s.Context()\n\tdep := \"echo-no-containerport\"\n\ts.ApplyApp(ctx, dep, \"deploy/\"+dep)\n\tdefer s.KubectlOk(ctx, \"delete\", \"deploy\", dep)\n\trequire := s.Require()\n\trequire.NoError(s.Kubectl(ctx, \"expose\", \"deploy\", dep, \"--port\", \"80\", \"--target-port\", \"8080\", \"--name\", dep+\"-\"+\"80\"))\n\tdefer s.KubectlOk(ctx, \"delete\", \"svc\", dep+\"-\"+\"80\")\n\trequire.NoError(s.Kubectl(ctx, \"expose\", \"deploy\", dep, \"--port\", \"81\", \"--target-port\", \"8081\", \"--name\", dep+\"-\"+\"81\"))\n\tdefer s.KubectlOk(ctx, \"delete\", \"svc\", dep+\"-\"+\"81\")\n\n\tportTest := func(svcPort, targetPort string) {\n\t\tctx := s.Context()\n\t\tsvc := dep + \"-\" + svcPort\n\n\t\tlocalPort, cancel := itest.StartLocalHttpEchoServer(ctx, svc)\n\t\tdefer cancel()\n\t\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", \"-p\", fmt.Sprintf(\"%d:%s\", localPort, svcPort), dep)\n\t\tdefer itest.TelepresenceOk(ctx, \"leave\", dep)\n\t\titest.PingInterceptedEchoServer(ctx, svc, svcPort)\n\t}\n\ts.Run(\"port 80\", func() {\n\t\tportTest(\"80\", \"8080\")\n\t})\n\ts.Run(\"port 81\", func() {\n\t\tportTest(\"81\", \"8081\")\n\t})\n}\n\nfunc (s *connectedSuite) Test_UnnamedUdpAndTcpPort() {\n\tctx := s.Context()\n\tdep := \"echo-udp-tcp-unnamed\"\n\ts.ApplyApp(ctx, dep, \"deploy/\"+dep)\n\tdefer s.KubectlOk(ctx, \"delete\", \"deploy\", dep)\n\n\trequire := s.Require()\n\trequire.NoError(s.Kubectl(ctx, \"expose\", \"deploy\", dep, \"--port\", \"80\", \"--protocol\", \"UDP\", \"--target-port\", \"8080\", \"--name\", \"echo-udp\"))\n\tdefer s.KubectlOk(ctx, \"delete\", \"svc\", \"echo-udp\")\n\trequire.NoError(s.Kubectl(ctx, \"expose\", \"deploy\", dep, \"--port\", \"80\", \"--protocol\", \"TCP\", \"--target-port\", \"8080\", \"--name\", \"echo-tcp\"))\n\tdefer s.KubectlOk(ctx, \"delete\", \"svc\", \"echo-tcp\")\n\n\tsvcPort := \"80\"\n\ts.Run(\"TCP port 80\", func() {\n\t\tctx := s.Context()\n\t\tlocalPort, cancel := itest.StartLocalHttpEchoServer(ctx, \"echo-tcp\")\n\t\tdefer cancel()\n\t\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", \"--service\", \"echo-tcp\", \"-p\", fmt.Sprintf(\"%d:%s\", localPort, svcPort), dep)\n\t\ts.CapturePodLogs(ctx, dep, \"traffic-agent\", s.AppNamespace())\n\t\tdefer itest.TelepresenceOk(ctx, \"leave\", dep)\n\t\titest.PingInterceptedEchoServer(ctx, \"echo-tcp\", svcPort)\n\t})\n\n\ts.Run(\"UDP port 80\", func() {\n\t\tctx := s.Context()\n\t\trequire := s.Require()\n\t\tlocalPortCh := make(chan int, 1)\n\n\t\t// Start a local interceptor service that prints some relevant messages\n\t\tgo func() {\n\t\t\tlc := net.ListenConfig{}\n\t\t\tpc, err := lc.ListenPacket(ctx, \"udp\", \":0\")\n\t\t\taddr := pc.LocalAddr().(*net.UDPAddr)\n\t\t\tlocalPortCh <- addr.Port\n\t\t\tclose(localPortCh)\n\t\t\trequire.NoError(err)\n\t\t\tbuf := [0x100]byte{}\n\t\t\tfor err == nil {\n\t\t\t\tvar rr net.Addr\n\t\t\t\tvar n int\n\t\t\t\t_ = pc.SetReadDeadline(time.Now().Add(10 * time.Millisecond))\n\t\t\t\tn, rr, err = pc.ReadFrom(buf[:])\n\t\t\t\tif n > 0 {\n\t\t\t\t\tmsg := string(buf[0:n])\n\t\t\t\t\tclog.Infof(ctx, \"Local UDP server received %q\", msg)\n\t\t\t\t\t_, werr := pc.WriteTo([]byte(fmt.Sprintf(\"received message %q\", msg)), rr)\n\t\t\t\t\trequire.NoError(werr)\n\t\t\t\t}\n\t\t\t\tif err != nil && strings.Contains(err.Error(), \"timeout\") {\n\t\t\t\t\terr = ctx.Err()\n\t\t\t\t}\n\t\t\t}\n\t\t\tclog.Debug(ctx, \"UDP end\")\n\t\t}()\n\t\tvar localPort int\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase localPort = <-localPortCh:\n\t\t}\n\n\t\titest.TelepresenceOk(ctx, \"loglevel\", \"trace\")\n\t\tdefer itest.TelepresenceOk(ctx, \"loglevel\", \"debug\")\n\t\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", \"--service\", \"echo-udp\", \"-p\", fmt.Sprintf(\"%d:%s\", localPort, svcPort), dep)\n\t\ts.CapturePodLogs(ctx, dep, \"traffic-agent\", s.AppNamespace())\n\t\tdefer itest.TelepresenceOk(ctx, \"leave\", dep)\n\n\t\tpingPong := func(conn net.Conn, msg string) {\n\t\t\t_ = conn.SetDeadline(time.Now().Add(10 * time.Second))\n\t\t\tbm := []byte(msg)\n\t\t\tn, err := conn.Write(bm)\n\t\t\trequire.NoError(err)\n\t\t\trequire.Equal(len(bm), n)\n\t\t\tbuf := [0x100]byte{}\n\t\t\tn, err = conn.Read(buf[:])\n\t\t\trequire.NoError(err)\n\t\t\trequire.Equal(fmt.Sprintf(\"received message %q\", msg), string(buf[0:n]))\n\t\t}\n\n\t\tinteract := func(i int, wg *sync.WaitGroup) {\n\t\t\tdefer wg.Done()\n\t\t\ttime.Sleep(time.Second)\n\t\t\tconn, err := net.Dial(\"udp\", fmt.Sprintf(\"%s:80\", \"echo-udp\"))\n\t\t\trequire.NoError(err)\n\t\t\tdefer conn.Close()\n\n\t\t\tpingPong(conn, fmt.Sprintf(\"%d: 12345678\", i))\n\t\t\tpingPong(conn, fmt.Sprintf(\"%d: a slightly longer message\", i))\n\t\t}\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(5)\n\t\tfor i := 0; i < 5; i++ {\n\t\t\tgo interact(i, &wg)\n\t\t}\n\t\twg.Wait()\n\t})\n}\n\n// TestSameContainerPort tests the use-case where multiple services using\n// the same container port.\nfunc (s *connectedSuite) Test_SameContainerPort() {\n\tctx := s.Context()\n\tdep := \"echo-stp\"\n\ts.ApplyApp(ctx, \"echo-same-target-port\", \"deploy/\"+dep)\n\tdefer func() {\n\t\ts.KubectlOk(ctx, \"delete\", \"deploy\", dep)\n\t}()\n\n\tportTest := func(svcPort string) {\n\t\tctx := s.Context()\n\t\tlocalPort, cancel := itest.StartLocalHttpEchoServer(ctx, dep)\n\t\tdefer cancel()\n\t\titest.TelepresenceOk(ctx, \"intercept\", \"--mount=false\", \"-p\", fmt.Sprintf(\"%d:%s\", localPort, svcPort), dep)\n\t\tdefer itest.TelepresenceOk(ctx, \"leave\", dep)\n\n\t\t// Both ports are now intercepted because the intercept is on the container port\n\t\titest.PingInterceptedEchoServer(ctx, dep, \"80\")\n\t\titest.PingInterceptedEchoServer(ctx, dep, \"8080\")\n\t}\n\ts.Run(\"port 80\", func() {\n\t\tportTest(\"eighty\")\n\t})\n\ts.Run(\"port 8080\", func() {\n\t\tportTest(\"eighty-eighty\")\n\t})\n}\n"
  },
  {
    "path": "integration_test/namespaces_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/labels\"\n)\n\ntype nsSuite struct {\n\tnss []namespaceData\n\titest.Suite\n}\n\nfunc (s *nsSuite) SuiteName() string {\n\treturn \"Namespaces\"\n}\n\nfunc init() {\n\titest.AddClusterSuite(func(ctx context.Context) itest.TestingSuite {\n\t\treturn &nsSuite{Suite: itest.Suite{Harness: itest.NewContextHarness(ctx)}}\n\t})\n}\n\nconst namespaceTpl = `\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: {{.Name}}\n  labels:\n    purpose: tp-cli-testing\n{{- with .Labels }}\n{{ toYaml . | nindent 4 }}\n{{- end }}`\n\ntype namespaceData struct {\n\tName   string\n\tLabels map[string]string\n}\n\nfunc (s *nsSuite) SetupSuite() {\n\tif !(s.ClientIsVersion(\">2.21.x\") && s.ManagerIsVersion(\">2.21.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility tests. Namespace selector was introduced in 2.22.0\")\n\t}\n\ts.nss = []namespaceData{\n\t\t{\n\t\t\t\"manager\",\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"alpha\",\n\t\t\tmap[string]string{\n\t\t\t\t\"phase\":  \"alpha\",\n\t\t\t\t\"deploy\": \"dev\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"beta\",\n\t\t\tmap[string]string{\n\t\t\t\t\"phase\":  \"beta\",\n\t\t\t\t\"deploy\": \"dev\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"gamma\",\n\t\t\tmap[string]string{\n\t\t\t\t\"phase\":  \"beta\",\n\t\t\t\t\"deploy\": \"dev\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"rc\",\n\t\t\tmap[string]string{\n\t\t\t\t\"phase\":  \"rc\",\n\t\t\t\t\"deploy\": \"staging\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"ga\",\n\t\t\tmap[string]string{\n\t\t\t\t\"phase\":  \"ga\",\n\t\t\t\t\"deploy\": \"prod\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"patch\",\n\t\t\tmap[string]string{\n\t\t\t\t\"phase\":  \"patch\",\n\t\t\t\t\"deploy\": \"prod\",\n\t\t\t},\n\t\t},\n\t}\n\tctx := s.Context()\n\twg := new(sync.WaitGroup)\n\twg.Add(len(s.nss))\n\tfor _, ns := range s.nss {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tdata, err := itest.EvalTemplate(namespaceTpl, ns)\n\t\t\ts.NoError(err)\n\t\t\ts.NoError(itest.Kubectl(dos.WithStdin(ctx, bytes.NewReader(data)), \"\", \"apply\", \"-f\", \"-\"))\n\t\t\titest.ApplyEchoService(ctx, \"echo\", ns.Name, 80)\n\t\t}()\n\t}\n\twg.Wait()\n\terr := itest.Kubectl(ctx, s.nss[0].Name, \"apply\", \"-f\", filepath.Join(itest.GetOSSRoot(ctx), \"testdata\", \"k8s\", \"client_sa.yaml\"))\n\ts.NoError(err, \"failed to create connect ServiceAccount\")\n}\n\nfunc (s *nsSuite) AmendSuiteContext(ctx context.Context) context.Context {\n\treturn itest.WithUser(ctx, \"manager:\"+itest.TestUser)\n}\n\nfunc (s *nsSuite) managerNamespace() string {\n\treturn s.nss[0].Name\n}\n\nfunc (s *nsSuite) TearDownSuite() {\n\tctx := s.Context()\n\twg := new(sync.WaitGroup)\n\twg.Add(len(s.nss))\n\tfor _, ns := range s.nss {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\t_ = itest.Kubectl(ctx, \"\", \"delete\", \"ns\", ns.Name)\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc (s *nsSuite) Test_NamespacesClusterWide() {\n\tctx := s.Context()\n\tctx = itest.WithNamespaces(ctx, &itest.Namespaces{\n\t\tNamespace: s.managerNamespace(),\n\t})\n\ts.TelepresenceHelmInstallOK(ctx, false)\n\tdefer s.UninstallTrafficManager(ctx, \"manager\")\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"alpha\")\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\tst := itest.TelepresenceStatusOk(ctx)\n\ts.Greater(len(st.UserDaemon.MappedNamespaces), len(s.nss))\n}\n\nfunc (s *nsSuite) Test_NamespacesDynamic() {\n\tctx := itest.WithNamespaces(s.Context(), &itest.Namespaces{\n\t\tNamespace: s.managerNamespace(),\n\t\tSelector: &labels.Selector{\n\t\t\tMatchExpressions: []*labels.Requirement{\n\t\t\t\t{\n\t\t\t\t\tKey:      \"phase\",\n\t\t\t\t\tOperator: labels.OperatorIn,\n\t\t\t\t\tValues:   []string{\"alpha\"},\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:      \"deploy\",\n\t\t\t\t\tOperator: labels.OperatorIn,\n\t\t\t\t\tValues:   []string{\"dev\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ts.TelepresenceHelmInstallOK(ctx, false)\n\tdefer s.UninstallTrafficManager(ctx, \"manager\")\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"alpha\")\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\trq := s.Require()\n\tst := itest.TelepresenceStatusOk(ctx)\n\trq.Len(st.UserDaemon.MappedNamespaces, 1)\n\n\titest.TelepresenceDisconnectOk(ctx)\n\t_, se, err := itest.Telepresence(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"beta\")\n\trq.Error(err)\n\trq.Contains(se, \"beta is not managed\")\n\n\t// Switch to just using label \"deploy=dev\"\n\tctx = itest.WithNamespaces(s.Context(), &itest.Namespaces{\n\t\tNamespace: s.managerNamespace(),\n\t\tSelector: &labels.Selector{\n\t\t\tMatchExpressions: []*labels.Requirement{{\n\t\t\t\tKey:      \"deploy\",\n\t\t\t\tOperator: labels.OperatorIn,\n\t\t\t\tValues:   []string{\"dev\"},\n\t\t\t}},\n\t\t},\n\t})\n\n\trestartCount := func() int {\n\t\tpods := itest.RunningPods(ctx, agentconfig.ManagerAppName, s.managerNamespace())\n\t\tif len(pods) == 1 {\n\t\t\tfor _, cs := range pods[0].Status.ContainerStatuses {\n\t\t\t\tif cs.Name == agentconfig.ManagerAppName {\n\t\t\t\t\treturn int(cs.RestartCount)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn -1\n\t}\n\n\trq.Equal(0, restartCount())\n\ts.TelepresenceHelmInstallOK(ctx, true)\n\n\t// A dynamic change of namespaces should not result in a traffic-manager restart\n\trq.Equal(0, restartCount())\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"beta\")\n\tst = itest.TelepresenceStatusOk(ctx)\n\trq.Len(st.UserDaemon.MappedNamespaces, 3)\n\titest.TelepresenceDisconnectOk(ctx)\n\n\t// Add yet another namespace that matches \"deploy=dev\"\n\tdata, err := itest.EvalTemplate(namespaceTpl, namespaceData{\n\t\t\"delta\",\n\t\tmap[string]string{\n\t\t\t\"phase\":  \"test\",\n\t\t\t\"deploy\": \"dev\",\n\t\t},\n\t})\n\trq.NoError(err)\n\trq.NoError(itest.Kubectl(dos.WithStdin(ctx, bytes.NewReader(data)), \"\", \"apply\", \"-f\", \"-\"))\n\tdefer func() {\n\t\ts.NoError(itest.Kubectl(ctx, \"\", \"delete\", \"namespace\", \"delta\"))\n\t}()\n\n\t// Upgrade, to ensure that the proper roles and rolebindings are installed.\n\ts.TelepresenceHelmInstallOK(ctx, true)\n\trq.Equal(0, restartCount())\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"delta\")\n\tst = itest.TelepresenceStatusOk(ctx)\n\trq.Len(st.UserDaemon.MappedNamespaces, 4)\n\n\titest.ApplyEchoService(ctx, \"echo\", \"delta\", 80)\n\n\t// Check that list output includes the service from \"delta\"\n\tlst := itest.TelepresenceOk(ctx, \"list\")\n\trq.Contains(lst, \"deployment echo:\")\n\titest.TelepresenceDisconnectOk(ctx)\n\n\t// Delete and recreate the namespace\n\ts.NoError(itest.Kubectl(ctx, \"\", \"delete\", \"namespace\", \"delta\"))\n\trq.NoError(itest.Kubectl(dos.WithStdin(ctx, bytes.NewReader(data)), \"\", \"apply\", \"-f\", \"-\"))\n\n\t// Upgrade, to ensure that the proper roles and rolebindings are installed.\n\ts.TelepresenceHelmInstallOK(ctx, true)\n\trq.Equal(0, restartCount())\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"delta\")\n\tst = itest.TelepresenceStatusOk(ctx)\n\trq.Len(st.UserDaemon.MappedNamespaces, 4)\n\n\titest.ApplyEchoService(ctx, \"echo\", \"delta\", 80)\n\n\t// Check that list output still includes the service from \"delta\"\n\tlst = itest.TelepresenceOk(ctx, \"list\")\n\trq.Contains(lst, \"deployment echo:\")\n\titest.TelepresenceDisconnectOk(ctx)\n}\n\nfunc (s *nsSuite) Test_NamespacesStatic() {\n\tctx := itest.WithNamespaces(s.Context(), &itest.Namespaces{\n\t\tNamespace: s.managerNamespace(),\n\t\tSelector: &labels.Selector{\n\t\t\tMatchExpressions: []*labels.Requirement{\n\t\t\t\t{\n\t\t\t\t\tKey:      labels.NameLabelKey,\n\t\t\t\t\tOperator: labels.OperatorIn,\n\t\t\t\t\tValues:   []string{\"alpha\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\ts.TelepresenceHelmInstallOK(ctx, false)\n\tdefer s.UninstallTrafficManager(ctx, \"manager\")\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"alpha\")\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\trq := s.Require()\n\tst := itest.TelepresenceStatusOk(ctx)\n\trq.Len(st.UserDaemon.MappedNamespaces, 1)\n\n\titest.TelepresenceDisconnectOk(ctx)\n\t_, se, err := itest.Telepresence(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"beta\")\n\trq.Error(err)\n\trq.Contains(se, \"beta is not managed\")\n\n\t// Switch to just using both alpha and beta\n\tctx = itest.WithNamespaces(s.Context(), &itest.Namespaces{\n\t\tNamespace: s.managerNamespace(),\n\t\tSelector: &labels.Selector{\n\t\t\tMatchExpressions: []*labels.Requirement{{\n\t\t\t\tKey:      labels.NameLabelKey,\n\t\t\t\tOperator: labels.OperatorIn,\n\t\t\t\tValues:   []string{\"alpha\", \"beta\"},\n\t\t\t}},\n\t\t},\n\t})\n\n\tgetPodName := func() (podName string) {\n\t\trq.Eventually(func() bool {\n\t\t\tpods := itest.RunningPods(ctx, agentconfig.ManagerAppName, s.managerNamespace())\n\t\t\tif len(pods) == 1 {\n\t\t\t\tpodName = pods[0].Name\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn false\n\t\t}, 16*time.Second, 4*time.Second)\n\t\treturn podName\n\t}\n\n\ttmPodName := getPodName()\n\ts.TelepresenceHelmInstallOK(ctx, true)\n\n\t// A static change must force a restart of the traffic-manager, or the new permissions will not come into effect.\n\ttmPodNameAfter := getPodName()\n\trq.NotEqual(tmPodName, tmPodNameAfter)\n\n\titest.TelepresenceOk(ctx, \"connect\", \"--manager-namespace\", s.managerNamespace(), \"--namespace\", \"beta\")\n\tst = itest.TelepresenceStatusOk(ctx)\n\trq.Len(st.UserDaemon.MappedNamespaces, 2)\n\titest.TelepresenceDisconnectOk(ctx)\n}\n"
  },
  {
    "path": "integration_test/not_connected_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/labels\"\n)\n\ntype notConnectedSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *notConnectedSuite) SuiteName() string {\n\treturn \"NotConnected\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &notConnectedSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *notConnectedSuite) TearDownTest() {\n\titest.TelepresenceQuitOk(s.Context())\n}\n\nfunc (s *notConnectedSuite) Test_ConnectWithCommand() {\n\tctx := s.Context()\n\texe, _ := s.Executable()\n\tstdout := s.TelepresenceConnect(ctx, \"--\", exe, \"status\")\n\ts.Contains(stdout, \"Connected to context\")\n\ts.Contains(stdout, \"Kubernetes context:\")\n}\n\nfunc (s *notConnectedSuite) Test_InvalidKubeconfig() {\n\tctx := s.Context()\n\tpath := \"/dev/null\"\n\tif runtime.GOOS == \"windows\" {\n\t\tpath = \"C:\\\\NUL\"\n\t}\n\tbadEnvCtx := itest.WithEnv(ctx, map[string]string{\"KUBECONFIG\": path})\n\t_, stderr, err := itest.Telepresence(badEnvCtx, \"connect\")\n\ts.Contains(stderr, \"kubeconfig has no context definition\")\n\ts.Error(err)\n}\n\nfunc (s *notConnectedSuite) Test_NonExistentContext() {\n\tctx := s.Context()\n\t_, stderr, err := itest.Telepresence(ctx, \"connect\", \"--context\", \"not-likely-to-exist\")\n\ts.Error(err)\n\ts.Contains(stderr, \"context was not found\")\n}\n\nfunc (s *notConnectedSuite) Test_ConnectingToOtherNamespace() {\n\tctx := s.Context()\n\n\tsuffix := itest.GetGlobalHarness(s.HarnessContext()).Suffix()\n\tappSpace2, mgrSpace2 := itest.AppAndMgrNSName(suffix + \"-2\")\n\titest.CreateNamespaces(ctx, appSpace2, mgrSpace2)\n\tdefer itest.DeleteNamespaces(ctx, appSpace2, mgrSpace2)\n\n\ts.Run(\"Installs Successfully\", func() {\n\t\tctx := itest.WithNamespaces(s.Context(), &itest.Namespaces{\n\t\t\tNamespace: mgrSpace2,\n\t\t\tSelector:  labels.SelectorFromNames(appSpace2),\n\t\t})\n\t\ts.TelepresenceHelmInstallOK(ctx, false)\n\t})\n\n\ts.Run(\"Can be connected to with --manager-namespace-flag\", func() {\n\t\tctx := s.Context()\n\t\titest.TelepresenceQuitOk(ctx)\n\n\t\t// Set the config to some nonsense to verify that the flag wins\n\t\tctx = itest.WithConfig(ctx, func(cfg client.Config) {\n\t\t\tcfg.Cluster().DefaultManagerNamespace = \"daffy-duck\"\n\t\t})\n\t\tctx = itest.WithUser(ctx, mgrSpace2+\":\"+itest.TestUser)\n\t\tstdout := itest.TelepresenceOk(ctx, \"connect\", \"--namespace\", appSpace2, \"--manager-namespace=\"+mgrSpace2)\n\t\ts.Contains(stdout, \"Connected to context\")\n\t\tstdout = itest.TelepresenceOk(ctx, \"status\")\n\t\ts.Regexp(`Manager namespace\\s+: `+mgrSpace2, stdout)\n\t})\n\n\ts.Run(\"Can be connected to with defaultManagerNamespace config\", func() {\n\t\tctx := s.Context()\n\t\titest.TelepresenceQuitOk(ctx)\n\t\tctx = itest.WithConfig(ctx, func(cfg client.Config) {\n\t\t\tcfg.Cluster().DefaultManagerNamespace = mgrSpace2\n\t\t})\n\t\tstdout := itest.TelepresenceOk(itest.WithUser(ctx, \"default\"), \"connect\", \"--namespace\", appSpace2)\n\t\ts.Contains(stdout, \"Connected to context\")\n\t\tstdout = itest.TelepresenceOk(ctx, \"status\")\n\t\ts.Regexp(`Manager namespace\\s+: `+mgrSpace2, stdout)\n\t})\n\n\ts.Run(\"Uninstalls Successfully\", func() {\n\t\ts.UninstallTrafficManager(s.Context(), mgrSpace2)\n\t})\n}\n\nfunc (s *notConnectedSuite) Test_ReportsNotConnected() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\titest.TelepresenceDisconnectOk(ctx)\n\tstdout := itest.TelepresenceOk(ctx, \"version\")\n\trxVer := regexp.QuoteMeta(s.ClientVersion().String())\n\ts.Regexp(fmt.Sprintf(`Client\\s*: v%s`, rxVer), stdout)\n\ts.Regexp(fmt.Sprintf(`Root Daemon\\s*: v%s`, rxVer), stdout)\n\ts.Regexp(fmt.Sprintf(`User Daemon\\s*: v%s`, rxVer), stdout)\n\ts.Regexp(`Traffic Manager\\s*: not connected`, stdout)\n}\n\n// Test_CreateAndRunIndividualPod tess that pods can be created without a workload.\nfunc (s *notConnectedSuite) Test_CreateAndRunIndividualPod() {\n\tout := s.KubectlOk(s.Context(), \"run\", \"-i\", \"busybox\", \"--rm\", \"--image\", \"curlimages/curl\", \"--restart\", \"Never\", \"--\", \"ls\", \"/etc\")\n\tlines := bufio.NewScanner(strings.NewReader(out))\n\thostsFound := false\n\tfor lines.Scan() {\n\t\tif lines.Text() == \"hosts\" {\n\t\t\thostsFound = true\n\t\t\tbreak\n\t\t}\n\t}\n\ts.True(hostsFound, \"remote ls command did not find /etc/hosts\")\n}\n"
  },
  {
    "path": "integration_test/otel_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\ntype otelSuite struct {\n\titest.Suite\n\titest.NamespacePair\n}\n\nfunc (s *otelSuite) SuiteName() string {\n\treturn \"Otel\"\n}\n\nfunc init() {\n\titest.AddNamespacePairSuite(\"-otel\", func(h itest.NamespacePair) itest.TestingSuite {\n\t\treturn &otelSuite{Suite: itest.Suite{Harness: h}, NamespacePair: h}\n\t})\n}\n\nfunc (s *otelSuite) SetupSuite() {\n\tif _, ok := os.LookupEnv(\"HTTP_INTERCEPT_STRESS_TEST\"); !ok {\n\t\ts.T().Skip(\"Run this stress manually. It's too demanding for the CI infrastructure.\")\n\t\treturn\n\t}\n\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\n\tcmd := itest.Command(ctx, \"helm\", \"upgrade\", \"--install\", \"cert-manager\", \"jetstack/cert-manager\", \"--namespace\", s.AppNamespace(), \"--version\", \"v1.8.0\", \"--set\", \"installCRDs=true\")\n\tout, err := cmd.CombinedOutput()\n\ts.Require().NoError(err, \"helm repo add: %s\", out)\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = itest.Run(ctx, \"helm\", \"uninstall\", \"cert-manager\", \"--namespace\", s.AppNamespace())\n\t\t}\n\t}()\n\n\totelDir := filepath.Join(\"testdata\", \"otel\")\n\tcmd = itest.Command(ctx, \"helm\", \"upgrade\", \"--install\", \"otel-operator\", \"open-telemetry/opentelemetry-operator\",\n\t\t\"--namespace\", s.AppNamespace(),\n\t\t\"--create-namespace\",\n\t\t\"--set\", \"manager.collectorImage.repository=otel/opentelemetry-collector-k8s\",\n\t\t\"--set\", \"admissionWebhooks.certManager.enabled=false\",\n\t\t\"--set\", \"admissionWebhooks.autoGenerateCert.enabled=true\",\n\t\t\"--set\", \"manager.extraArgs={--enable-go-instrumentation}\",\n\t\t\"--values\", filepath.Join(otelDir, \"helm-yamls\", \"otel-operator.yml\"),\n\t)\n\tout, err = cmd.CombinedOutput()\n\ts.Require().NoError(err, \"helm install\", string(out))\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = itest.Run(ctx, \"helm\", \"uninstall\", \"otel-operator\", \"--namespace\", s.AppNamespace())\n\t\t}\n\t}()\n\n\tcmd = itest.Command(ctx, \"helm\", \"upgrade\", \"--install\", \"--namespace\", s.AppNamespace(), \"alloy\", \"grafana/alloy\")\n\tout, err = cmd.CombinedOutput()\n\ts.Require().NoError(err, \"helm install\", string(out))\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = itest.Run(ctx, \"helm\", \"uninstall\", \"alloy\", \"--namespace\", s.AppNamespace())\n\t\t}\n\t}()\n\n\t// Wait for cert-manager to be ready. The job might have been completed already.\n\t_ = s.Kubectl(ctx, \"wait\", \"--for=condition=complete\", \"--timeout=60s\", \"job/cert-manager-startupapicheck\")\n\t_ = s.RolloutStatusWait(ctx, \"deploy/cert-manager-webhook\")\n\t_ = s.RolloutStatusWait(ctx, \"deploy/cert-manager\")\n\ttime.Sleep(30 * time.Second)\n\n\tvar instrumentationData []byte\n\tinstrumentationData, err = os.ReadFile(filepath.Join(otelDir, \"instrumentation.yml\"))\n\ts.Require().NoError(err)\n\tinstrumentationData = bytes.ReplaceAll(instrumentationData, []byte(\"__NAMESPACE__\"), []byte(s.AppNamespace()))\n\terr = s.Kubectl(dos.WithStdin(ctx, bytes.NewReader(instrumentationData)), \"apply\", \"-f\", \"-\")\n\ts.Require().NoError(err)\n\n\tvar so string\n\tso, err = s.TelepresenceHelmInstall(ctx, false, \"--set\", \"logLevel=trace\", \"--set\", \"agentInjector.mutationAware=false\")\n\ts.Require().NoError(err, \"telepresence install\", so)\n}\n\nfunc (s *otelSuite) TearDownSuite() {\n\tctx := s.Context()\n\ts.UninstallTrafficManager(ctx, s.ManagerNamespace())\n\tcmd := itest.Command(ctx, \"helm\", \"uninstall\", \"otel-operator\", \"--namespace\", s.AppNamespace())\n\tout, err := cmd.CombinedOutput()\n\ts.NoError(err, \"helm uninstall otel-operator\", string(out))\n\tcmd = itest.Command(ctx, \"helm\", \"uninstall\", \"cert-manager\", \"--namespace\", s.AppNamespace())\n\tout, err = cmd.CombinedOutput()\n\ts.NoError(err, \"helm uninstall cert-manager\", string(out))\n\tcmd = itest.Command(ctx, \"helm\", \"uninstall\", \"alloy\", \"--namespace\", s.AppNamespace())\n\tout, err = cmd.CombinedOutput()\n\ts.NoError(err, \"helm uninstall alloy\", string(out))\n}\n"
  },
  {
    "path": "integration_test/pod_cidr_test.go",
    "content": "package integration_test\n\nimport (\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype podCIDRSuite struct {\n\titest.Suite\n\titest.NamespacePair\n}\n\nfunc (s *podCIDRSuite) SuiteName() string {\n\treturn \"PodCIDRStrategy\"\n}\n\nfunc init() {\n\titest.AddNamespacePairSuite(\"\", func(h itest.NamespacePair) itest.TestingSuite {\n\t\treturn &podCIDRSuite{Suite: itest.Suite{Harness: h}, NamespacePair: h}\n\t})\n}\n\nfunc (s *podCIDRSuite) Test_PodCIDRStrategy() {\n\tctx := s.Context()\n\tconnected := false\n\n\ts.TelepresenceHelmInstallOK(ctx, false)\n\tdefer func() {\n\t\tif connected {\n\t\t\titest.TelepresenceQuitOk(ctx)\n\t\t}\n\t\ts.UninstallTrafficManager(ctx, s.ManagerNamespace())\n\t}()\n\ts.TelepresenceConnect(ctx)\n\tconnected = true\n\tsi := itest.TelepresenceStatusOk(ctx)\n\titest.TelepresenceQuitOk(ctx)\n\tconnected = false\n\n\tsubnetsAsStrings := func(snn []netip.Prefix) []string {\n\t\tsns := make([]string, len(snn))\n\t\tfor i, sn := range snn {\n\t\t\tsns[i] = sn.String()\n\t\t}\n\t\treturn sns\n\t}\n\tclog.Infof(ctx, \"subnets %v\", si.RootDaemon.Subnets)\n\tpodCIDRs := subnetsAsStrings(si.RootDaemon.Subnets[1:])\n\n\ttests := []struct {\n\t\tname        string\n\t\tvalues      map[string]any\n\t\twantSubnets []string\n\t}{\n\t\t{\n\t\t\t\"environment\",\n\t\t\tmap[string]any{\n\t\t\t\t\"podCIDRs\":        append(podCIDRs, \"199.199.50.228/30\"),\n\t\t\t\t\"podCIDRStrategy\": \"environment\",\n\t\t\t},\n\t\t\tappend(podCIDRs, \"199.199.50.228/30\"),\n\t\t},\n\t}\n\n\tvDir := s.T().TempDir()\n\tvFile := filepath.Join(vDir, \"values.yaml\")\n\n\tfor _, tt := range tests {\n\t\ts.Run(tt.name, func() {\n\t\t\trq := s.Require()\n\t\t\tvy, err := yaml.Marshal(tt.values)\n\t\t\trq.NoError(err)\n\t\t\trq.NoError(os.WriteFile(vFile, vy, 0o644))\n\t\t\ts.TelepresenceHelmInstallOK(ctx, true, \"--no-hooks\", \"-f\", vFile)\n\t\t\ts.TelepresenceConnect(ctx)\n\t\t\tconnected = true\n\t\t\tsi := itest.TelepresenceStatusOk(ctx)\n\t\t\titest.TelepresenceQuitOk(ctx)\n\t\t\tconnected = false\n\t\t\trd := si.RootDaemon\n\t\t\trq.NotNil(rd)\n\t\t\trq.Greater(len(rd.Subnets), 1)\n\t\t\tsns := subnetsAsStrings(rd.Subnets[1:])\n\t\t\tfor _, ws := range tt.wantSubnets {\n\t\t\t\ts.Contains(sns, ws)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "integration_test/podscaling_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\n// Test_RestartInterceptedPod build belongs to the interceptMountSuite because we want to\n// verify that the mount survives the scaling.\nfunc (s *interceptMountSuite) Test_RestartInterceptedPod() {\n\tassert := s.Assert()\n\trequire := s.Require()\n\tctx := s.Context()\n\trx := regexp.MustCompile(fmt.Sprintf(`Intercept name\\s*: %s\\s+State\\s*: ([^\\n]+)\\n`, s.ServiceName()))\n\n\t// Scale down to zero pods\n\trequire.NoError(s.Kubectl(ctx, \"scale\", \"deploy\", s.ServiceName(), \"--replicas\", \"0\"))\n\tscaleUp := true\n\tdefer func() {\n\t\tif scaleUp {\n\t\t\tassert.NoError(s.Kubectl(ctx, \"scale\", \"deploy\", s.ServiceName(), \"--replicas\", \"1\"))\n\t\t}\n\t}()\n\n\t// Wait until the pods have terminated. This might take a long time (several minutes).\n\trequire.Eventually(func() bool { return len(s.runningPods(ctx)) == 0 }, 2*time.Minute, 6*time.Second)\n\n\t// Verify that intercept remains but that no agent is found. Use require here\n\t// to avoid a hanging os.Stat call unless this succeeds.\n\trequire.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\")\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tif match := rx.FindStringSubmatch(stdout); match != nil {\n\t\t\treturn match[1] == \"WAITING\" || strings.Contains(match[1], `No agent found for \"`+s.ServiceName()+`\"`)\n\t\t}\n\t\treturn false\n\t}, 30*time.Second, 3*time.Second)\n\n\t// Verify that volume mount is broken\n\ttime.Sleep(time.Second) // avoid a stat just when the intercept became inactive as it sometimes causes a hang\n\t_, err := os.Stat(filepath.Join(s.mountPoint, \"var\", \"run\"))\n\tassert.Error(err, \"Stat on <mount point>/var succeeded although no agent was found\")\n\n\t// Scale up again (start intercepted pod)\n\tassert.NoError(s.Kubectl(ctx, \"scale\", \"deploy\", s.ServiceName(), \"--replicas\", \"1\"))\n\tscaleUp = false\n\n\tassert.Eventually(func() bool { return len(s.runningPods(ctx)) == 1 }, itest.PodCreateTimeout(ctx), 6*time.Second)\n\ts.CapturePodLogs(ctx, s.ServiceName(), \"traffic-agent\", s.AppNamespace())\n\n\t// Verify that intercept becomes active\n\tassert.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\")\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"%s: %v\", stdout, err)\n\t\t\treturn false\n\t\t}\n\t\tif match := rx.FindStringSubmatch(stdout); match != nil {\n\t\t\tif match[1] == \"ACTIVE\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\tclog.Info(ctx, stdout)\n\t\treturn false\n\t}, 30*time.Second, 3*time.Second)\n\n\t// Verify that volume mount is restored\n\ttime.Sleep(time.Second) // avoid a stat just when the intercept became active as it sometimes causes a hang\n\tassert.Eventually(func() bool {\n\t\tst, err := os.Stat(filepath.Join(s.mountPoint, \"var\", \"run\"))\n\t\treturn err == nil && st.IsDir()\n\t}, 5*time.Second, time.Second)\n}\n\n// Test_StopInterceptedPodOfMany build belongs to the interceptMountSuite because we want to\n// verify that the mount survives the scaling.\nfunc (s *interceptMountSuite) Test_StopInterceptedPodOfMany() {\n\tassert := s.Assert()\n\trequire := s.Require()\n\tctx := s.Context()\n\n\t// Wait for exactly one active pod\n\tvar currentPod string\n\trequire.Eventually(func() bool {\n\t\tcurrentPods := s.runningPods(ctx)\n\t\tif len(currentPods) == 1 {\n\t\t\tcurrentPod = currentPods[0]\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}, 20*time.Second, 2*time.Second)\n\n\t// Scale up to two pods\n\trequire.NoError(s.Kubectl(ctx, \"scale\", \"deploy\", s.ServiceName(), \"--replicas\", \"2\"))\n\tdefer func() {\n\t\t_ = s.Kubectl(ctx, \"scale\", \"deploy\", s.ServiceName(), \"--replicas\", \"1\")\n\t\tassert.Eventually(\n\t\t\tfunc() bool {\n\t\t\t\treturn len(s.runningPods(ctx)) == 1\n\t\t\t}, time.Minute, time.Second)\n\t\ts.CapturePodLogs(ctx, s.ServiceName(), \"traffic-agent\", s.AppNamespace())\n\t}()\n\n\t// Wait for second pod to arrive\n\tassert.Eventually(func() bool { return len(s.runningPods(ctx)) == 2 }, itest.PodCreateTimeout(ctx), 6*time.Second)\n\ts.CapturePodLogs(ctx, s.ServiceName(), \"traffic-agent\", s.AppNamespace())\n\n\t// Delete the currently intercepted pod\n\trequire.NoError(s.Kubectl(ctx, \"delete\", \"pod\", currentPod))\n\n\t// Wait for that pod to disappear and then be recreated\n\tassert.Eventually(\n\t\tfunc() bool {\n\t\t\tpods := s.runningPods(ctx)\n\t\t\tfor _, zp := range pods {\n\t\t\t\tif zp == currentPod {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn len(pods) == 2\n\t\t}, time.Minute, time.Second)\n\ts.CapturePodLogs(ctx, s.ServiceName(), \"traffic-agent\", s.AppNamespace())\n\n\t// Verify that intercept is still active\n\trx := regexp.MustCompile(`Intercept name\\s*: ` + s.ServiceName() + `\\s+State\\s*: ([^\\n]+)\\n`)\n\tassert.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tclog.Debugf(ctx, \"match %q in %q\", rx.String(), stdout)\n\t\tif match := rx.FindStringSubmatch(stdout); match != nil {\n\t\t\treturn match[1] == \"ACTIVE\"\n\t\t}\n\t\treturn false\n\t}, 15*time.Second, time.Second)\n\n\t// Verify response from intercepting client\n\texpect := s.ServiceName() + \" from intercept at /\"\n\trequire.Eventually(func() bool {\n\t\thc := http.Client{Timeout: time.Second}\n\t\tresp, err := hc.Get(\"http://\" + s.ServiceName())\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tdefer resp.Body.Close()\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn false\n\t\t}\n\t\tif expect == string(body) {\n\t\t\treturn true\n\t\t}\n\t\tclog.Infof(ctx, \"%s != %s\", expect, string(body))\n\t\treturn false\n\t}, 30*time.Second, time.Second)\n\n\t// Verify that volume mount is restored\n\ttime.Sleep(3 * time.Second) // avoid a stat just when the intercept became active as it sometimes causes a hang\n\tst, err := os.Stat(filepath.Join(s.mountPoint, \"var\"))\n\trequire.NoError(err, \"Stat on <mount point>/var failed\")\n\trequire.True(st.IsDir(), \"<mount point>/var is not a directory\")\n}\n\n// Return the names of running pods with app=<service name>. Running here means\n// that at least one container is still running. I.e. the pod might well be terminating\n// but still considered running.\nfunc (s *interceptMountSuite) runningPods(ctx context.Context) []string {\n\treturn itest.RunningPodNames(ctx, s.ServiceName(), s.AppNamespace())\n}\n"
  },
  {
    "path": "integration_test/proxy_via_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\ntype proxyViaSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *proxyViaSuite) SuiteName() string {\n\treturn \"ProxyVia\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &proxyViaSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nconst (\n\tdomain   = \"mydomain.local\"\n\talias    = \"echo-home\"\n\tfqnAlias = alias + \".\" + domain\n)\n\nfunc (s *proxyViaSuite) SetupSuite() {\n\tif !s.ClientIsVersion(\">2.24.x\") {\n\t\ts.T().Skip(\"ProxyVia have some quirks in versions <2.25.0\")\n\t}\n\ts.Suite.SetupSuite()\n\ttpl := struct {\n\t\tAliasIP string\n\t\tAliases []string\n\t}{\n\t\tAliasIP: \"127.0.0.1\",\n\t\tAliases: []string{alias, fqnAlias},\n\t}\n\tif s.IsIPv6() {\n\t\ttpl.AliasIP = \"::1\"\n\t}\n\ts.ApplyTemplate(s.Context(), filepath.Join(\"testdata\", \"k8s\", \"echo-w-hostalias.goyaml\"), &tpl)\n\ts.NoError(s.RolloutStatusWait(s.Context(), \"deploy/echo\"))\n}\n\nfunc (s *proxyViaSuite) TearDownSuite() {\n\ts.DeleteSvcAndWorkload(s.Context(), \"deploy\", \"echo\")\n}\n\nfunc (s *proxyViaSuite) Test_ProxyViaLoopBack() {\n\tctx := s.Context()\n\tif s.IsIPv6() {\n\t\tctx = itest.WithConfig(ctx, func(config client.Config) {\n\t\t\tconfig.Routing().VirtualSubnet = netip.MustParsePrefix(\"abac:0de0::/64\")\n\t\t})\n\t}\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"client.dns.includeSuffixes={mydomain.local}\")\n\tdefer s.RollbackTM(ctx)\n\n\tif s.IsIPv6() {\n\t\ts.TelepresenceConnect(ctx, \"--proxy-via\", \"::1/128=echo\")\n\t} else {\n\t\ts.TelepresenceConnect(ctx, \"--proxy-via\", \"127.0.0.1/32=echo\")\n\t}\n\tdefer itest.TelepresenceQuitOk(ctx)\n\ts.CapturePodLogs(ctx, \"echo\", \"traffic-agent\", s.AppNamespace())\n\n\tvirtualSubnet := client.GetConfig(ctx).Routing().VirtualSubnet\n\n\ttests := []struct {\n\t\tname           string\n\t\thostName       string\n\t\texpectedOutput *regexp.Regexp\n\t}{\n\t\t{\n\t\t\t\"single-label\",\n\t\t\talias,\n\t\t\tregexp.MustCompile(\"Host: \" + alias + \":8080\"),\n\t\t},\n\t\t{\n\t\t\t\"fully-qualified\",\n\t\t\tfqnAlias,\n\t\t\tregexp.MustCompile(\"Host: \" + fqnAlias + \":8080\"),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\ts.Run(tt.name, func() {\n\t\t\trq := s.Require()\n\t\t\tvar vip netip.Addr\n\t\t\trq.Eventually(func() bool {\n\t\t\t\t// hostname will resolve to 127.0.0.1 remotely and then be translated into a virtual IP\n\t\t\t\tips, err := net.LookupIP(tt.hostName)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tif len(ips) != 1 {\n\t\t\t\t\tclog.Error(ctx, \"LookupIP did not return one IP\")\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tvar ok bool\n\t\t\t\tvip, ok = netip.AddrFromSlice(ips[0])\n\t\t\t\treturn ok\n\t\t\t}, 30*time.Second, 2*time.Second)\n\t\t\tclog.Infof(ctx, \"%s uses IP %s\", tt.hostName, vip)\n\t\t\trq.Truef(virtualSubnet.Contains(vip), \"virtualIPSubnet %s does not contain %s\", virtualSubnet, vip)\n\n\t\t\trq.Eventually(func() bool {\n\t\t\t\tout, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"2\", net.JoinHostPort(tt.hostName, \"8080\"))\n\t\t\t\tclog.Info(ctx, out)\n\t\t\t\treturn err == nil && tt.expectedOutput.MatchString(out)\n\t\t\t}, 10*time.Second, 2*time.Second)\n\t\t})\n\t}\n}\n\nfunc (s *proxyViaSuite) Test_ProxyViaEverything() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tst := itest.TelepresenceStatusOk(ctx)\n\titest.TelepresenceDisconnectOk(ctx)\n\tsns := st.RootDaemon.Subnets\n\tif len(sns) == 0 {\n\t\ts.T().Skip(\"Test cannot run unless client maps at least one subnet\")\n\t}\n\n\tif s.IsIPv6() {\n\t\tctx = itest.WithConfig(ctx, func(config client.Config) {\n\t\t\tconfig.Routing().VirtualSubnet = netip.MustParsePrefix(\"abac:0de0::/64\")\n\t\t})\n\t}\n\n\targs := make([]string, 0, len(sns)*2)\n\tfor _, sn := range sns {\n\t\targs = append(args, \"--proxy-via\", sn.String()+\"=echo\")\n\t}\n\ts.TelepresenceConnect(ctx, args...)\n\tst = itest.TelepresenceStatusOk(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\trq := s.Require()\n\trq.NotNil(st.RootDaemon)\n\trq.Len(st.RootDaemon.Subnets, 1) // Virtual subnet\n\trq.Eventually(func() bool {\n\t\tout, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"2\", \"echo\")\n\t\tclog.Infof(ctx, \"Output from echo service %s\", out)\n\t\treturn err == nil\n\t}, 10*time.Second, 2*time.Second)\n}\n\nfunc (s *proxyViaSuite) Test_ProxyViaAll() {\n\tctx := s.Context()\n\trq := s.Require()\n\tif s.IsIPv6() {\n\t\tctx = itest.WithConfig(ctx, func(config client.Config) {\n\t\t\tconfig.Routing().VirtualSubnet = netip.MustParsePrefix(\"abac:0de0::/64\")\n\t\t})\n\t}\n\n\ts.TelepresenceConnect(ctx, \"--proxy-via\", \"all=echo\")\n\tst := itest.TelepresenceStatusOk(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\trq.NotNil(st.RootDaemon)\n\trq.Len(st.RootDaemon.Subnets, 1) // Virtual subnet\n\trq.Eventually(func() bool {\n\t\tout, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"2\", \"echo\")\n\t\tclog.Infof(ctx, \"Output from echo service %s\", out)\n\t\treturn err == nil\n\t}, 30*time.Second, 2*time.Second)\n}\n\nfunc (s *proxyViaSuite) Test_ProxyViaAllAndMounts() {\n\tif s.IsCI() && runtime.GOOS == \"darwin\" {\n\t\ts.T().Skip(\"CI can't do user mounts on darwin\")\n\t}\n\tctx := s.Context()\n\trq := s.Require()\n\tif s.IsIPv6() {\n\t\tctx = itest.WithConfig(ctx, func(config client.Config) {\n\t\t\tconfig.Routing().VirtualSubnet = netip.MustParsePrefix(\"abac:0de0::/64\")\n\t\t})\n\t}\n\n\ts.TelepresenceConnect(ctx, \"--proxy-via\", \"all=echo\")\n\tst := itest.TelepresenceStatusOk(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\trq.NotNil(st.RootDaemon)\n\trq.Len(st.RootDaemon.Subnets, 1)\n\n\tvar mountPoint string\n\tif runtime.GOOS == \"windows\" {\n\t\tmountPoint = \"T:\"\n\t} else {\n\t\tvar err error\n\t\tmountPoint, err = os.MkdirTemp(\"\", \"mount-\") // Don't use the testing.Tempdir() because deletion is delayed.\n\t\ts.Require().NoError(err)\n\t\tdefer func() {\n\t\t\ttime.AfterFunc(time.Second, func() {\n\t\t\t\t_ = os.RemoveAll(mountPoint)\n\t\t\t})\n\t\t}()\n\t}\n\n\titest.TelepresenceOk(ctx, \"intercept\", \"--mount\", mountPoint, \"echo\")\n\n\t// Verify that volume mount is present and functional\n\ttime.Sleep(3 * time.Second) // avoid a stat just when the intercept became active as it sometimes causes a hang\n\tfst, err := os.Stat(filepath.Join(mountPoint, \"var\"))\n\trq.NoError(err, \"Stat on <mount point>/var failed\")\n\trq.True(fst.IsDir())\n}\n\nfunc (s *proxyViaSuite) Test_NeverProxySubnetIsOmitted() {\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tst := itest.TelepresenceStatusOk(ctx)\n\titest.TelepresenceDisconnectOk(ctx)\n\tsns := st.RootDaemon.Subnets\n\tif len(sns) == 0 {\n\t\ts.T().Skip(\"Test cannot run unless client maps at least one subnet\")\n\t}\n\tlogFile := filepath.Join(filelocation.AppUserLogDir(s.Context()), \"daemon.log\")\n\trootLog, err := os.Open(logFile)\n\ts.Require().NoError(err)\n\tdefer rootLog.Close()\n\n\tfor _, sn := range sns {\n\t\ts.Run(\"subnet \"+sn.String(), func() {\n\t\t\tctx := s.Context()\n\t\t\trq := s.Require()\n\n\t\t\tfs, err := rootLog.Stat()\n\t\t\trq.NoError(err)\n\t\t\tpos := fs.Size()\n\n\t\t\ts.TelepresenceConnect(ctx, \"--never-proxy\", sn.String())\n\t\t\titest.TelepresenceDisconnectOk(ctx)\n\t\t\t_, err = rootLog.Seek(pos, io.SeekStart)\n\t\t\trq.NoError(err)\n\t\t\tscn := bufio.NewScanner(rootLog)\n\t\t\tfound := false\n\n\t\t\tmsg := fmt.Sprintf(\"Dropping never-proxy %q\", sn.String())\n\t\t\tfor scn.Scan() {\n\t\t\t\ttxt := scn.Text()\n\t\t\t\tif strings.Contains(txt, msg) {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.Truef(found, \"Unable to find %q\", msg)\n\t\t})\n\t}\n\ts.Run(fmt.Sprintf(\"subnets %s\", sns), func() {\n\t\tctx := s.Context()\n\t\trq := s.Require()\n\n\t\tfs, err := rootLog.Stat()\n\t\trq.NoError(err)\n\t\tpos := fs.Size()\n\n\t\tsb := strings.Builder{}\n\t\tfor i, sn := range sns {\n\t\t\tif i > 0 {\n\t\t\t\tsb.WriteByte(',')\n\t\t\t}\n\t\t\tsb.WriteString(sn.String())\n\t\t}\n\t\ts.TelepresenceConnect(ctx, \"--never-proxy\", sb.String())\n\t\titest.TelepresenceDisconnectOk(ctx)\n\t\tfor _, sn := range sns {\n\t\t\t_, err = rootLog.Seek(pos, io.SeekStart)\n\t\t\trq.NoError(err)\n\t\t\tscn := bufio.NewScanner(rootLog)\n\t\t\tfound := false\n\n\t\t\tmsg := fmt.Sprintf(\"Dropping never-proxy %q\", sn.String())\n\t\t\tfor scn.Scan() {\n\t\t\t\ttxt := scn.Text()\n\t\t\t\tif strings.Contains(txt, msg) {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.Truef(found, \"Unable to find %q\", msg)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "integration_test/reconnect_session_test.go",
    "content": "package integration_test\n\nimport (\n\t\"net\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype reconnectSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tiptablesChain string\n\tsvc           string\n}\n\nfunc (s *reconnectSuite) SuiteName() string {\n\treturn \"Reconnect\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &reconnectSuite{\n\t\t\tSuite:          itest.Suite{Harness: h},\n\t\t\tTrafficManager: h,\n\t\t\tiptablesChain:  \"TEL2-CI\",\n\t\t\tsvc:            \"echo-easy\",\n\t\t}\n\t})\n}\n\nfunc (s *reconnectSuite) SetupSuite() {\n\tif !s.ClientIsVersion(\">2.21.x\") {\n\t\ts.T().Skip(`Not part of compatibility tests. Reconnect doesn't work well in Versions < 2.22.0`)\n\t}\n\ts.Suite.SetupSuite()\n\n\t// Add our iptables table, disable tests if we're unable to.\n\terr := itest.Run(s.Context(), \"sudo\", \"iptables\", \"-t\", \"filter\", \"-N\", s.iptablesChain)\n\tif err != nil {\n\t\ts.T().Skipf(\"Skipped, because sudo iptables fails: %v\", err)\n\t}\n\n\tctx := s.Context()\n\n\ts.ApplyApp(ctx, s.svc, \"deploy/\"+s.svc)\n}\n\nfunc (s *reconnectSuite) TearDownSuite() {\n\tctx := s.Context()\n\ts.DeleteSvcAndWorkload(ctx, \"deploy\", s.svc)\n\ts.NoError(itest.Run(ctx, \"sudo\", \"iptables\", \"-t\", \"filter\", \"-X\", s.iptablesChain))\n}\n\nfunc (s *reconnectSuite) Test_ReconnectAfterNetworkFailure() {\n\tctx := s.Context()\n\trq := s.Require()\n\tkc := itest.KubeConfig(ctx)\n\tcfg, err := clientcmd.LoadFromFile(kc)\n\trq.NoError(err)\n\n\t// Front the Kubernetes API with a socat, so that we can break the connection.\n\tc, ok := cfg.Contexts[cfg.CurrentContext]\n\trq.True(ok)\n\tcl, ok := cfg.Clusters[c.Cluster]\n\trq.True(ok)\n\tsu, err := url.Parse(cl.Server)\n\trq.NoError(err)\n\tips, err := net.LookupIP(su.Hostname())\n\trq.NoError(err)\n\trq.Len(ips, 1)\n\tkubeIP := ips[0]\n\n\t// Set up a chain that will drop all tcp packages destined for the kubernetes server.\n\trq.NoError(itest.Run(ctx, \"sudo\", \"iptables\", \"-t\", \"filter\", \"-A\", s.iptablesChain, \"-p\", \"tcp\",\n\t\t\"-d\", kubeIP.String(),\n\t\t\"--dport\", su.Port(),\n\t\t\"-j\", \"DROP\",\n\t))\n\tdefer func() {\n\t\t// Flush (i.e. clear) the chain\n\t\ts.NoError(itest.Run(ctx, \"sudo\", \"iptables\", \"-t\", \"filter\", \"-F\", s.iptablesChain))\n\t}()\n\n\tport, cancel := itest.StartLocalHttpEchoServer(ctx, s.svc)\n\tdefer cancel()\n\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceQuitOk(ctx)\n\n\titest.TelepresenceOk(ctx, \"intercept\", \"--port\", strconv.Itoa(port), \"--mount=false\", s.svc)\n\n\t// Send all tcp-packets in the OUTPUT chain to our iptablesChain. This effectively interrupts all communication\n\t// going from the telepresence daemons to the kubernetes server.\n\trq.NoError(itest.Run(ctx, \"sudo\", \"iptables\", \"-t\", \"filter\", \"-I\", \"OUTPUT\", \"-p\", \"tcp\", \"-j\", s.iptablesChain))\n\n\tvar restore atomic.Bool\n\trestore.Store(true)\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\trestoreOutput := func() {\n\t\tif restore.CompareAndSwap(true, false) {\n\t\t\twg.Done()\n\t\t\ts.NoError(itest.Run(ctx, \"sudo\", \"iptables\", \"-t\", \"filter\", \"-D\", \"OUTPUT\", \"-p\", \"tcp\", \"-j\", s.iptablesChain))\n\t\t}\n\t}\n\tdefer restoreOutput()\n\n\t// Restore connection after 7 seconds. This ensures that the first ping will fail.\n\ttime.AfterFunc(7*time.Second, restoreOutput)\n\n\t// Ping the intercepted service. The first ping will certainly fail, but eventually the connection will be restored.\n\titest.PingInterceptedEchoServer(ctx, s.svc, \"80\")\n\twg.Wait()\n}\n"
  },
  {
    "path": "integration_test/replace_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n)\n\ntype replaceSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tsvc     string\n\ttplPath string\n\ttpl     *itest.Generic\n}\n\nfunc (s *replaceSuite) SuiteName() string {\n\treturn \"Replace\"\n}\n\nfunc init() {\n\titest.AddConnectedSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &replaceSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h, svc: \"echo-server\"}\n\t})\n}\n\nfunc (s *replaceSuite) SetupSuite() {\n\tif !(s.ManagerIsVersion(\">2.21.x\") && s.ClientIsVersion(\">2.21.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility tests. The replace command was introduced in 2.22\")\n\t}\n\ts.Suite.SetupSuite()\n\ts.tplPath = filepath.Join(\"testdata\", \"k8s\", \"generic.goyaml\")\n\ts.tpl = &itest.Generic{\n\t\tName:       s.svc,\n\t\tTargetPort: \"http\",\n\t\tRegistry:   \"ghcr.io/telepresenceio\",\n\t\tImage:      \"echo-server:latest\",\n\t\tServicePorts: []itest.ServicePort{\n\t\t\t{\n\t\t\t\tNumber:     80,\n\t\t\t\tName:       \"http\",\n\t\t\t\tTargetPort: \"http-cp\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tNumber:     81,\n\t\t\t\tName:       \"extra\",\n\t\t\t\tTargetPort: \"extra-cp\",\n\t\t\t},\n\t\t},\n\t\tContainerPorts: []itest.ContainerPort{\n\t\t\t{\n\t\t\t\tNumber: 8080,\n\t\t\t\tName:   \"http-cp\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tNumber: 8081,\n\t\t\t\tName:   \"extra-cp\",\n\t\t\t},\n\t\t},\n\t\tEnvironment: []core.EnvVar{\n\t\t\t{\n\t\t\t\tName:  \"PORTS\",\n\t\t\t\tValue: \"8080,8081\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"LISTEN_ADDRESS\",\n\t\t\t\tValueFrom: &core.EnvVarSource{\n\t\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\t\tFieldPath: \"status.podIP\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tAnnotations: map[string]string{\n\t\t\tannotation.InjectTrafficAgent: \"enabled\",\n\t\t},\n\t}\n\tctx := s.Context()\n\ts.ApplyTemplate(ctx, s.tplPath, s.tpl)\n\ts.NoError(s.RolloutStatusWait(ctx, \"deploy/\"+s.svc))\n}\n\nfunc (s *replaceSuite) TearDownSuite() {\n\ts.DeleteTemplate(s.Context(), s.tplPath, s.tpl)\n}\n\nfunc (s *replaceSuite) Test_ReplaceWithMultiContainerPorts() {\n\tctx := s.Context()\n\t_, httpCancel := itest.StartLocalHttpEchoServerWithAddr(ctx, s.svc+\"-http\", \"localhost:8080\", nil)\n\tdefer httpCancel()\n\t_, extraCancel := itest.StartLocalHttpEchoServerWithAddr(ctx, s.svc+\"-extra\", \"localhost:8081\", nil)\n\tdefer extraCancel()\n\n\t// Use container port names here.\n\tso := itest.TelepresenceOk(ctx, \"replace\", s.svc)\n\tmustLeave := true\n\tdefer func() {\n\t\tif mustLeave {\n\t\t\titest.TelepresenceOk(ctx, \"leave\", s.svc)\n\t\t}\n\t}()\n\n\trq := s.Require()\n\trq.Contains(so, \"Using Deployment \"+s.svc)\n\n\titest.PingInterceptedEchoServer(ctx, fmt.Sprintf(\"%s/%s-http\", s.svc, s.svc), \"80\")\n\titest.PingInterceptedEchoServer(ctx, fmt.Sprintf(\"%s/%s-extra\", s.svc, s.svc), \"81\")\n\n\titest.TelepresenceOk(ctx, \"leave\", s.svc)\n\tmustLeave = false\n\n\t// Ensure that we now reach the original app again.\n\ts.Eventually(func() bool {\n\t\tout, err := itest.Output(ctx, \"curl\", \"--verbose\", \"--max-time\", \"1\", s.svc)\n\t\tclog.Infof(ctx, \"Received %s\", out)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"curl error %v\", err)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t}, 30*time.Second, 2*time.Second, \"Pod app is not reachable after ending the replace\")\n}\n"
  },
  {
    "path": "integration_test/restapi_test.go",
    "content": "package integration\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/restapi\"\n)\n\n// request is the JSON structure used by the echo-server /forward endpoint.\ntype request struct {\n\tURL     string            `json:\"url\"`\n\tHeaders map[string]string `json:\"headers,omitempty\"`\n\tMethod  string            `json:\"method,omitempty\"`\n\tBody    string            `json:\"body,omitempty\"`\n}\n\ntype restAPISuite struct {\n\titest.Suite\n\titest.NamespacePair\n\tsvc        string\n\tregistry   string\n\timage      string\n\tapiPort    uint16\n\tdaemonName string\n}\n\nfunc (s *restAPISuite) SuiteName() string {\n\treturn \"RESTful-API\"\n}\n\nfunc init() {\n\titest.AddNamespacePairSuite(\"\", func(h itest.NamespacePair) itest.TestingSuite {\n\t\treturn &restAPISuite{\n\t\t\tSuite:         itest.Suite{Harness: h},\n\t\t\tNamespacePair: h,\n\t\t\tsvc:           \"hello\",\n\t\t\tapiPort:       9980,\n\t\t\tdaemonName:    \"alpha\",\n\t\t\tregistry:      \"ghcr.io/telepresenceio\",\n\t\t\timage:         \"echo-server:latest\",\n\t\t}\n\t})\n}\n\nfunc (s *restAPISuite) SetupSuite() {\n\tif !(s.ManagerIsVersion(\">2.24.x\") && s.ClientIsVersion(\">2.24.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility suite\")\n\t}\n\tif s.IsCI() && runtime.GOOS != \"linux\" {\n\t\ts.T().Skip(\"CI can't run linux docker containers inside non-linux runners\")\n\t\treturn\n\t}\n\ts.Suite.SetupSuite()\n\tctx := s.Context()\n\n\ts.TelepresenceHelmInstallOK(ctx, false, \"--set\", fmt.Sprintf(\"telepresenceAPI.port=%d\", s.apiPort))\n\tdep := &itest.Generic{\n\t\tName:     s.svc,\n\t\tRegistry: s.registry,\n\t\tImage:    s.image,\n\t\tEnvironment: []core.EnvVar{\n\t\t\t{\n\t\t\t\tName:  \"TELEPRESENCE_API_PORT\",\n\t\t\t\tValue: strconv.Itoa(int(s.apiPort)),\n\t\t\t}, {\n\t\t\t\tName:  \"PORTS\",\n\t\t\t\tValue: \"8080,8081\",\n\t\t\t},\n\t\t},\n\t\tAnnotations: map[string]string{\n\t\t\tannotation.InjectTrafficAgent: \"enabled\",\n\t\t},\n\t\tServicePorts: []itest.ServicePort{\n\t\t\t{\n\t\t\t\tNumber:     80,\n\t\t\t\tName:       \"http\",\n\t\t\t\tTargetPort: \"http\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tNumber:     81,\n\t\t\t\tName:       \"check\",\n\t\t\t\tTargetPort: \"check\",\n\t\t\t},\n\t\t},\n\t\tContainerPorts: []itest.ContainerPort{\n\t\t\t{\n\t\t\t\tName:   \"http\",\n\t\t\t\tNumber: 8080,\n\t\t\t},\n\t\t\t{\n\t\t\t\tName:   \"check\",\n\t\t\t\tNumber: 8081,\n\t\t\t},\n\t\t},\n\t}\n\ts.NoError(dep.Apply(ctx, s.AppNamespace()))\n\ts.CapturePodLogs(ctx, s.svc, \"traffic-agent\", s.AppNamespace())\n\ts.CapturePodLogs(ctx, s.svc, \"\", s.AppNamespace())\n\ts.TelepresenceConnect(ctx, \"--docker\", \"--name\", s.daemonName)\n}\n\nfunc (s *restAPISuite) TearDownSuite() {\n\tctx := s.Context()\n\titest.TelepresenceQuit(ctx)\n\ts.KubectlOk(ctx, \"delete\", \"svc,deploy\", s.svc)\n\ts.UninstallTrafficManager(ctx, s.ManagerNamespace())\n}\n\nfunc (s *restAPISuite) AmendSuiteContext(ctx context.Context) context.Context {\n\treturn itest.WithConfig(ctx, func(cfg client.Config) {\n\t\tcfg.Intercept().UseFtp = false\n\t})\n}\n\nfunc (s *restAPISuite) curlAPIServer(ctx context.Context, port uint16, path string, headers map[string]string, myArgs ...string) (string, error) {\n\tapiReq := &request{\n\t\tHeaders: headers,\n\t\tURL:     fmt.Sprintf(\"http://${TELEPRESENCE_API_HOST}:${TELEPRESENCE_API_PORT}/%s?containerPort=%d\", path, port),\n\t}\n\tapiReqJSON, err := json.Marshal(apiReq)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to marshal request: %w\", err)\n\t}\n\targs := append([]string{\n\t\t\"curl\",\n\t\t\"--silent\",\n\t\t\"--max-time\", \"2\",\n\t\t\"-H\", \"Content-Type: application/json\",\n\t\t\"--request\", \"POST\",\n\t\t\"--data\", string(apiReqJSON),\n\t\tfmt.Sprintf(\"http://%s/forward\", s.svc),\n\t}, myArgs...)\n\tso, se, err := itest.Telepresence(ctx, args...)\n\tif se != \"\" {\n\t\tclog.Errorf(ctx, \"stderr: %s\", se)\n\t}\n\treturn so, err\n}\n\nfunc (s *restAPISuite) startIntercept(ctx context.Context, meta, header string, errCh chan<- error, wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\tdefer close(errCh)\n\targs := make([]string, 0, 15)\n\targs = append(args, \"intercept\", s.svc)\n\tif meta != \"\" {\n\t\targs = append(args, \"--metadata\", meta)\n\t}\n\tif header != \"\" {\n\t\targs = append(args, \"--http-header\", header)\n\t}\n\targs = append(args, \"--port\", \"8080:80\", \"--mount=false\", \"--docker-run\", \"--\", \"--rm\", \"--name\", s.svc+\"-local\", s.registry+\"/\"+s.image)\n\tso, se, err := itest.Telepresence(ctx, args...)\n\tif so != \"\" {\n\t\tclog.Infof(ctx, \"stdout: %s\", so)\n\t}\n\tif se != \"\" {\n\t\tclog.Errorf(ctx, \"stderr: %s\", se)\n\t}\n\tif err != nil {\n\t\terrCh <- err\n\t}\n}\n\nfunc (s *restAPISuite) waitForInterceptReady(ctx context.Context, errCh <-chan error) error {\n\tctx, cancel := context.WithTimeout(ctx, 30*time.Second)\n\tdefer cancel()\n\tticker := time.NewTicker(500 * time.Millisecond)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase err := <-errCh:\n\t\t\treturn err\n\t\tcase <-ticker.C:\n\t\t\tst, err := itest.TelepresenceStatus(ctx)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tfor _, ic := range st.ContainerizedDaemon.Intercepts {\n\t\t\t\tif ic.Name == s.svc {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *restAPISuite) Test_RestAPI_GlobalConsume() {\n\tctx := s.Context()\n\trq := s.Require()\n\n\tso, err := s.curlAPIServer(ctx, 8080, \"consume-here\", nil)\n\trq.NoError(err)\n\tclog.Infof(ctx, \"stdout: %s\", so)\n\tvar jv any\n\terr = json.Unmarshal([]byte(so), &jv)\n\trq.NoError(err)\n\tyes, ok := jv.(bool)\n\trq.True(ok)\n\trq.True(yes)\n\n\t// When exiting, first cancel the intercept, then wait for it to exit.\n\twg := &sync.WaitGroup{}\n\tiCtx, cancel := context.WithCancel(ctx)\n\tdefer func() {\n\t\tcancel()\n\t\twg.Wait()\n\t}()\n\twg.Add(1)\n\terrCh := make(chan error, 1)\n\tgo s.startIntercept(iCtx, \"\", \"\", errCh, wg)\n\trq.NoError(s.waitForInterceptReady(iCtx, errCh))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", s.svc)\n\n\tso, err = s.curlAPIServer(ctx, 8080, \"consume-here\", map[string]string{restapi.HeaderCallerInterceptID: \"${TELEPRESENCE_INTERCEPT_ID}\"})\n\trq.NoError(err)\n\tjv = nil\n\terr = json.Unmarshal([]byte(so), &jv)\n\trq.NoError(err)\n\tclog.Infof(ctx, \"stdout: %s\", so)\n\n\t// A global intercept will always hit the client, and the client's API server will always return true for the intercept id.\n\tyes, ok = jv.(bool)\n\trq.True(ok)\n\trq.True(yes)\n}\n\nfunc (s *restAPISuite) Test_RestAPI_GlobalInfo() {\n\tctx := s.Context()\n\trq := s.Require()\n\n\tso, err := s.curlAPIServer(ctx, 8080, \"intercept-info\", nil)\n\trq.NoError(err)\n\tclog.Infof(ctx, \"stdout: %s\", so)\n\tvar jv any\n\terr = json.Unmarshal([]byte(so), &jv)\n\trq.NoError(err)\n\tinfo, ok := jv.(map[string]any)\n\trq.True(ok)\n\tyes, ok := info[\"intercepted\"].(bool)\n\trq.True(ok)\n\ts.False(yes)\n\n\twg := &sync.WaitGroup{}\n\tiCtx, cancel := context.WithCancel(ctx)\n\tdefer func() {\n\t\tcancel()\n\t\twg.Wait()\n\t}()\n\twg.Add(1)\n\terrCh := make(chan error, 1)\n\tgo s.startIntercept(iCtx, \"my:data\", \"\", errCh, wg)\n\trq.NoError(s.waitForInterceptReady(iCtx, errCh))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", s.svc)\n\n\tso, err = s.curlAPIServer(ctx, 8080, \"intercept-info\", map[string]string{restapi.HeaderCallerInterceptID: \"${TELEPRESENCE_INTERCEPT_ID}\"})\n\trq.NoError(err)\n\tjv = nil\n\terr = json.Unmarshal([]byte(so), &jv)\n\trq.NoError(err)\n\tclog.Infof(ctx, \"stdout: %s\", so)\n\tinfo, ok = jv.(map[string]any)\n\trq.True(ok)\n\tyes, ok = info[\"intercepted\"].(bool)\n\trq.True(ok)\n\ts.True(yes)\n\n\tdata, ok := info[\"metadata\"].(map[string]any)\n\trq.True(ok)\n\ts.Equal(\"data\", data[\"my\"])\n}\n\nfunc (s *restAPISuite) Test_RestAPI_FilteredConsume() {\n\tctx := s.Context()\n\twg := &sync.WaitGroup{}\n\tiCtx, cancel := context.WithCancel(ctx)\n\tdefer func() {\n\t\tcancel()\n\t\twg.Wait()\n\t}()\n\twg.Add(1)\n\terrCh := make(chan error, 1)\n\tgo s.startIntercept(iCtx, \"\", \"x:y\", errCh, wg)\n\ts.Require().NoError(s.waitForInterceptReady(iCtx, errCh))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", s.svc)\n\n\ttts := []struct {\n\t\tname          string\n\t\tcontainerPort uint16\n\t\tapiHeaders    map[string]string\n\t\targs          []string\n\t\texpected      bool\n\t}{\n\t\t// Query the remote API server without any headers. It must respond true because this doesn't match any intercept.\n\t\t{\n\t\t\tname:          \"query-remote-without-headers\",\n\t\t\tcontainerPort: 8080,\n\t\t\texpected:      true,\n\t\t},\n\t\t// Query the remote API server with the intercepted header. The intercept is matched, so the remote API server\n\t\t// should tell the remote app to not consume the request.\n\t\t{\n\t\t\tname:          \"query-remote-with-headers\",\n\t\t\tcontainerPort: 8080,\n\t\t\tapiHeaders:    map[string]string{\"x\": \"y\"},\n\t\t\texpected:      false,\n\t\t},\n\t\t// Query the local API server without the intercepted header. The intercept is not matched, so the local API server\n\t\t// should tell the local app to not consume the request.\n\t\t{\n\t\t\tname:          \"query-local-without-headers\",\n\t\t\tcontainerPort: 8080,\n\t\t\texpected:      false,\n\t\t\tapiHeaders:    map[string]string{restapi.HeaderCallerInterceptID: \"${TELEPRESENCE_INTERCEPT_ID}\"},\n\t\t\targs:          []string{\"-H\", \"x:y\"},\n\t\t},\n\t\t// Query the local API server with the intercepted header. The intercept is matched, so the local API server\n\t\t// should tell the local app to consume the request.\n\t\t{\n\t\t\tname:          \"query-local-with-headers\",\n\t\t\tcontainerPort: 8080,\n\t\t\tapiHeaders:    map[string]string{restapi.HeaderCallerInterceptID: \"${TELEPRESENCE_INTERCEPT_ID}\", \"x\": \"y\"},\n\t\t\targs:          []string{\"-H\", \"x:y\"},\n\t\t\texpected:      true,\n\t\t},\n\t}\n\tfor _, tt := range tts {\n\t\ts.Run(tt.name, func() {\n\t\t\tctx := s.Context()\n\t\t\trq := s.Require()\n\t\t\tso, err := s.curlAPIServer(ctx, tt.containerPort, \"consume-here\", tt.apiHeaders, tt.args...)\n\t\t\trq.NoError(err)\n\t\t\tclog.Infof(ctx, \"stdout: %s\", so)\n\t\t\tvar jv any\n\t\t\terr = json.Unmarshal([]byte(so), &jv)\n\t\t\trq.NoError(err)\n\t\t\tyes, ok := jv.(bool)\n\t\t\trq.True(ok)\n\t\t\ts.Equal(tt.expected, yes)\n\t\t})\n\t}\n}\n\nfunc (s *restAPISuite) Test_RestAPI_FilteredInfo() {\n\tctx := s.Context()\n\twg := &sync.WaitGroup{}\n\tiCtx, cancel := context.WithCancel(ctx)\n\tdefer func() {\n\t\tcancel()\n\t\twg.Wait()\n\t}()\n\twg.Add(1)\n\terrCh := make(chan error, 1)\n\tgo s.startIntercept(iCtx, \"my:data\", \"x:y\", errCh, wg)\n\ts.Require().NoError(s.waitForInterceptReady(iCtx, errCh))\n\tdefer itest.TelepresenceOk(ctx, \"leave\", s.svc)\n\n\ttts := []struct {\n\t\tname          string\n\t\tcontainerPort uint16\n\t\tapiHeaders    map[string]string\n\t\targs          []string\n\t\tintercepted   bool\n\t\tclientSide    bool\n\t\tmyMetadata    string\n\t}{\n\t\t// Query the remote API server without any headers. It must respond with client-side=false and intercepted=false because this doesn't match any intercept.'\n\t\t{\n\t\t\tname:          \"query-remote-without-headers\",\n\t\t\tcontainerPort: 8080,\n\t\t\tintercepted:   false,\n\t\t\tclientSide:    false,\n\t\t\tmyMetadata:    \"\",\n\t\t},\n\t\t// Query the remote API server with the intercepted header. It must respond with client-side=false and intercepted=true because the remote app is intercepted.\n\t\t{\n\t\t\tname:          \"query-remote-with-headers\",\n\t\t\tcontainerPort: 8080,\n\t\t\tapiHeaders:    map[string]string{\"x\": \"y\"},\n\t\t\tintercepted:   true,\n\t\t\tclientSide:    false,\n\t\t\tmyMetadata:    \"data\",\n\t\t},\n\t\t// Query the local API server without the intercepted header. The local API server must respond with client-side=true and intercepted=false.\n\t\t{\n\t\t\tname:          \"query-local-without-headers\",\n\t\t\tcontainerPort: 8080,\n\t\t\tapiHeaders:    map[string]string{restapi.HeaderCallerInterceptID: \"${TELEPRESENCE_INTERCEPT_ID}\"},\n\t\t\tintercepted:   false,\n\t\t\tclientSide:    true,\n\t\t\targs:          []string{\"-H\", \"x:y\"},\n\t\t\tmyMetadata:    \"\",\n\t\t},\n\t\t// Query the local API server with the intercepted header. The local API server must respond with client-side=true and intercepted=true\n\t\t{\n\t\t\tname:          \"query-local-with-headers\",\n\t\t\tcontainerPort: 8080,\n\t\t\tapiHeaders:    map[string]string{restapi.HeaderCallerInterceptID: \"${TELEPRESENCE_INTERCEPT_ID}\", \"x\": \"y\"},\n\t\t\tintercepted:   true,\n\t\t\tclientSide:    true,\n\t\t\targs:          []string{\"-H\", \"x:y\"},\n\t\t\tmyMetadata:    \"data\",\n\t\t},\n\t}\n\tfor _, tt := range tts {\n\t\ts.Run(tt.name, func() {\n\t\t\tctx := s.Context()\n\t\t\trq := s.Require()\n\t\t\tso, err := s.curlAPIServer(ctx, tt.containerPort, \"intercept-info\", tt.apiHeaders, tt.args...)\n\t\t\trq.NoError(err)\n\t\t\tclog.Infof(ctx, \"stdout: %s\", so)\n\t\t\tvar jv any\n\t\t\terr = json.Unmarshal([]byte(so), &jv)\n\t\t\trq.NoError(err)\n\t\t\tclog.Infof(ctx, \"stdout: %s\", so)\n\t\t\tinfo, ok := jv.(map[string]any)\n\t\t\trq.True(ok)\n\t\t\tyes, ok := info[\"intercepted\"].(bool)\n\t\t\trq.True(ok)\n\t\t\ts.Equal(tt.intercepted, yes)\n\n\t\t\t// All intercepts have the same metadata.\n\t\t\tdata, ok := info[\"metadata\"].(map[string]any)\n\t\t\tif tt.myMetadata != \"\" {\n\t\t\t\trq.True(ok)\n\t\t\t\ts.Equal(tt.myMetadata, data[\"my\"])\n\t\t\t} else {\n\t\t\t\ts.False(ok)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "integration_test/single_service_test.go",
    "content": "package integration_test\n\nimport (\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype singleServiceSuite struct {\n\titest.Suite\n\titest.SingleService\n}\n\nfunc (s *singleServiceSuite) SuiteName() string {\n\treturn \"SingleService\"\n}\n\nfunc init() {\n\titest.AddSingleServiceSuite(\"\", \"echo\", func(h itest.SingleService) itest.TestingSuite {\n\t\treturn &singleServiceSuite{Suite: itest.Suite{Harness: h}, SingleService: h}\n\t})\n}\n"
  },
  {
    "path": "integration_test/subdomain_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nfunc (s *connectedSuite) Test_PodWithSubdomain() {\n\tc := s.Context()\n\ts.ApplyApp(c, \"echo-w-subdomain\", \"deploy/echo-subsonic\")\n\tdefer func() {\n\t\ts.NoError(s.Kubectl(c, \"delete\", \"svc\", \"subsonic\"))\n\t\ts.NoError(s.Kubectl(c, \"delete\", \"deploy\", \"echo-subsonic\"))\n\t}()\n\n\tlookupHost := func(host string) {\n\t\tvar err error\n\t\ts.Eventually(func() bool {\n\t\t\tc, cancel := context.WithTimeout(c, 1800*time.Millisecond)\n\t\t\tdefer cancel()\n\t\t\tclog.Info(c, \"LookupHost(\"+host+\")\")\n\t\t\t_, err = net.DefaultResolver.LookupHost(c, host)\n\t\t\treturn err == nil\n\t\t}, 10*time.Second, 2*time.Second, \"%s did not resolve: %v\", host, err)\n\t}\n\tlookupHost(\"echo.subsonic.\" + s.AppNamespace())\n\tlookupHost(\"echo.subsonic.\" + s.AppNamespace() + \".svc.cluster.local\")\n}\n"
  },
  {
    "path": "integration_test/svcdomain_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nfunc (s *connectedSuite) Test_SvcDomain() {\n\tc := s.Context()\n\ts.ApplyEchoService(c, \"echo\", 8080)\n\tdefer s.DeleteSvcAndWorkload(c, \"deploy\", \"echo\")\n\n\thost := fmt.Sprintf(\"echo.%s.svc\", s.AppNamespace())\n\ts.Eventuallyf(func() bool {\n\t\tc, cancel := context.WithTimeout(c, 1800*time.Millisecond)\n\t\tdefer cancel()\n\t\tclog.Info(c, \"LookupHost(\"+host+\")\")\n\t\t_, err := net.DefaultResolver.LookupHost(c, host)\n\t\treturn err == nil\n\t}, 10*time.Second, 2*time.Second, \"%s did not resolve\", host)\n}\n"
  },
  {
    "path": "integration_test/testdata/apiserveraccess/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/matcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/restapi\"\n)\n\n// This service is meant for testing the cluster side Telepresence API service.\n//\n// Publish image to cluster:\n//\n//\tko publish -B ./integration_test/testdata/apiserveraccess [--insecure-registry]\n//\n// Deploy it:\n//\n//\tkubectl apply -f ./k8s/apitest.yaml\n//\n// Run it locally using an intercept with -- so that TELEPRESENCE_INTERCEPT_ID is propagated in the env\nfunc main() {\n\tc, cancel := context.WithCancel(log.MakeBaseLogger(context.Background(), os.Stderr, \"DEBUG\"))\n\tsigs := make(chan os.Signal, 1)\n\tsignal.Notify(sigs, os.Interrupt, unix.SIGTERM)\n\tdefer func() {\n\t\tsignal.Stop(sigs)\n\t\tcancel()\n\t}()\n\n\tgo func() {\n\t\tselect {\n\t\tcase <-sigs:\n\t\t\tcancel()\n\t\tcase <-c.Done():\n\t\t}\n\t\t<-sigs\n\t\tos.Exit(1)\n\t}()\n\n\tif err := run(c); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc run(c context.Context) error {\n\tif lv, ok := os.LookupEnv(\"LOG_LEVEL\"); ok {\n\t\tclog.SetTreeLevel(c, clog.MustParseLevel(lv))\n\t}\n\n\tap, ok := os.LookupEnv(\"APP_PORT\")\n\tif !ok {\n\t\tap = \"8080\"\n\t}\n\t_, err := strconv.ParseUint(ap, 10, 16)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"the value %q of env APP_PORT is not a valid port number\", ap)\n\t}\n\n\tln, err := net.Listen(\"tcp\", \"localhost:\"+ap)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/healthz\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\n\tif apiUrl, err := apiURL(); err == nil {\n\t\tconsumeHereURL := apiUrl + \"/consume-here\"\n\t\tinterceptInfoURL := apiUrl + \"/intercept-info\"\n\t\tmux.HandleFunc(\"/consume-here\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tvar b bool\n\t\t\tintercepted(c, consumeHereURL, r.FormValue(\"path\"), w, r, &b)\n\t\t})\n\t\tmux.HandleFunc(\"/intercept-info\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tii := restapi.InterceptInfo{}\n\t\t\tintercepted(c, interceptInfoURL, r.FormValue(\"path\"), w, r, &ii)\n\t\t})\n\t} else {\n\t\tmux.HandleFunc(\"/consume-here\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\t_ = json.MarshalWrite(w, false)\n\t\t})\n\t\tmux.HandleFunc(\"/intercept-info\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\tii := restapi.InterceptInfo{}\n\t\t\t_ = json.MarshalWrite(w, &ii)\n\t\t})\n\t}\n\n\tinfo := fmt.Sprintf(\"API test server on %v\", ln.Addr())\n\tclog.Infof(c, \"%s started\", info)\n\tdefer clog.Infof(c, \"%s ended\", info)\n\tsvc := http.Server{Handler: mux, BaseContext: func(listener net.Listener) context.Context {\n\t\treturn c\n\t}}\n\tgo func() {\n\t\t_ = svc.Serve(ln)\n\t}()\n\t<-c.Done()\n\treturn svc.Shutdown(context.Background())\n}\n\nconst interceptIdEnv = \"TELEPRESENCE_INTERCEPT_ID\"\n\n// apiURL creates the generic URL needed to access the service\nfunc apiURL() (string, error) {\n\tpe := os.Getenv(agentconfig.EnvAPIPort)\n\tif _, err := strconv.ParseUint(pe, 10, 16); err != nil {\n\t\treturn \"\", fmt.Errorf(\"value %q of env %s does not represent a valid port number\", pe, agentconfig.EnvAPIPort)\n\t}\n\treturn \"http://localhost:\" + pe, nil\n}\n\n// doRequest calls the consume-here endpoint with the given headers and returns the result\nfunc doRequest(c context.Context, rqUrl string, path string, hm map[string]string, objTemplate any, er *restapi.ErrorResponse) (int, error) {\n\trq, err := http.NewRequest(\"GET\", rqUrl+\"?path=\"+url.QueryEscape(path), nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\trq.Header = make(http.Header, len(hm)+1)\n\trq.Header.Set(\"X-Telepresence-Caller-Intercept-Id\", os.Getenv(interceptIdEnv))\n\tfor k, v := range hm {\n\t\trq.Header.Set(k, v)\n\t}\n\tclog.Debugf(c, \"%s with headers\\n%s\", rqUrl, matcher.HeaderStringer(rq.Header))\n\trs, err := http.DefaultClient.Do(rq)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tdefer rs.Body.Close()\n\n\tif rs.StatusCode == http.StatusOK {\n\t\terr = json.UnmarshalRead(rs.Body, objTemplate)\n\t} else {\n\t\t// Make an attempt to decode a json error.\n\t\t_ = json.UnmarshalRead(rs.Body, er)\n\t}\n\treturn rs.StatusCode, err\n}\n\nfunc intercepted(c context.Context, url string, path string, w http.ResponseWriter, r *http.Request, objTemplate any) {\n\thm := make(map[string]string, len(r.Header))\n\n\t// The \"X-With-\" prefix is used as a backdoor to avoid triggering intercepts during test. It's\n\t// stripped off here.\n\tfor h := range r.Header {\n\t\thm[strings.TrimPrefix(h, \"X-With-\")] = r.Header.Get(h)\n\t}\n\n\t// The \"X-Without-\" prefix is used when the headers that trigger an intercept must be included in\n\t// order for the intercept to take place, but should be removed in the subsequent query. Both\n\t// the \"X-Without-\" headers and the header they refer to are stripped off here.\n\tfor h := range hm {\n\t\tif hw := strings.TrimPrefix(h, \"X-Without-\"); h != hw {\n\t\t\tdelete(hm, h)\n\t\t\tdelete(hm, hw)\n\t\t}\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\n\ter := restapi.ErrorResponse{}\n\tif status, err := doRequest(c, url, path, hm, objTemplate, &er); err != nil {\n\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\tfmt.Fprintf(w, \"failed to execute http request: %v\", err)\n\t} else {\n\t\tw.WriteHeader(status)\n\t\tif status == http.StatusOK {\n\t\t\terr = json.MarshalWrite(w, objTemplate)\n\t\t} else if er.Error != \"\" {\n\t\t\terr = json.MarshalWrite(w, er)\n\t\t}\n\t\tif err != nil {\n\t\t\tclog.Error(c, err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "integration_test/testdata/cli-container/Dockerfile",
    "content": "FROM telepresence\n\nRUN apk add --no-cache bash curl\n\nENTRYPOINT [\"/bin/bash\"]"
  },
  {
    "path": "integration_test/testdata/count-server/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"sync/atomic\"\n)\n\nfunc main() {\n\tcounter := int64(0)\n\thttp.HandleFunc(\"/count\", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, atomic.LoadInt64(&counter)) })\n\n\thttp.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tatomic.AddInt64(&counter, 1)\n\t})\n\tlog.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n"
  },
  {
    "path": "integration_test/testdata/echo-server/Dockerfile",
    "content": "FROM golang:alpine AS base\n\n# Build Delve\nRUN go install github.com/go-delve/delve/cmd/dlv@latest\n\nWORKDIR /echo-server\nCOPY go.mod .\nCOPY go.sum .\n# Get dependencies - will also be cached if we won't change mod/sum\nRUN go mod download\nCOPY main.go .\n\nFROM base AS builder\nRUN go build -o echo-server .\n\nFROM base AS development\nRUN go build -gcflags=\"all=-N -l\" -o echo-server .\nEXPOSE 40000\nCMD [\"/go/bin/dlv\", \"--listen=:40000\", \"--headless=true\", \"--api-version=2\", \"--accept-multiclient\", \"exec\", \"/echo-server/echo-server\"]\n\nFROM alpine AS production\nCOPY --from=builder /echo-server/echo-server /\nCOPY certs certs\nCMD [\"/echo-server\"]\n"
  },
  {
    "path": "integration_test/testdata/echo-server/certs/tls.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDZTCCAk2gAwIBAgIURwxYS26yunKI6OcJh4loNDXvguMwDQYJKoZIhvcNAQEL\nBQAwNDEYMBYGA1UEAwwPdGVsZXByZXNlbmNlLmlvMRgwFgYDVQQKDA90ZWxlcHJl\nc2VuY2UuaW8wHhcNMjUwOTI3MDEzODEwWhcNMjYwOTI3MDEzODEwWjA0MRgwFgYD\nVQQDDA90ZWxlcHJlc2VuY2UuaW8xGDAWBgNVBAoMD3RlbGVwcmVzZW5jZS5pbzCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIhAUP3zIQTmXtbJWAv/wX9u\n1iFVTS/1ty8pVdS3yPWngZbd3SGDdtHkm0Lis/PDBiP2IWHjHQP8YC332J3vgiH2\nnSTJxXAlJz0kpJyT0Z1NV3d1It+1QBnh5FFN5XvZYZ6poE+EMlG2nKCA9g0O8xHg\neOQXPUWN1PsjZtU6bmvGuzWyzgKadZZztA9TY0nQIbGljCoYrBB7/lfcoIsz1IHc\na+wUlkeKgoZJ5L+xMx5tkNlpBPtCOP1Le/xu1MHPFKTJd5G08gzb/uRGJ4tnXtR0\nAYd57wTMSs7lLS+cfzySCuu10t/Emql1HtJGQ0EdzjCBYjzbGFTo8u9mS3XxIWMC\nAwEAAaNvMG0wHQYDVR0OBBYEFMfgpuHxQF45KO2YsOsZz1KsytlAMB8GA1UdIwQY\nMBaAFMfgpuHxQF45KO2YsOsZz1KsytlAMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R\nBBMwEYIPdGVsZXByZXNlbmNlLmlvMA0GCSqGSIb3DQEBCwUAA4IBAQBeyH/EDsB6\n7k5wVjuDwo+4USRjJ1sF6hvxBBxsRpwict84ErM3FaQK/v/Jf/hgtvFmTTlMn3EM\nvuzQEgsLsFXlCct2F7I6F5ywICCStMLfzvC17lU8/v4I0seDS7FeQzx133uYCA9i\nPJrKj+D6Ay+pp0sl6vkUlHVm9FiD+taMY8eWT2FvQSg6n2g2UEyOA9LwWA0KagBk\nEC1vkd+w7FZSMqFI+vdej+25EYqzAuC6Cw1rbWnURhKp8+GiUS8jLjFaSpeJmuPp\n7N4mlWr9t3AatYtekjhnqFXIktQS9inKe+F4h11XieHCzM3jyDvCFFGs5tWVV4/G\nc7aA+ZfJ/GXY\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "integration_test/testdata/echo-server/certs/tls.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCIQFD98yEE5l7W\nyVgL/8F/btYhVU0v9bcvKVXUt8j1p4GW3d0hg3bR5JtC4rPzwwYj9iFh4x0D/GAt\n99id74Ih9p0kycVwJSc9JKSck9GdTVd3dSLftUAZ4eRRTeV72WGeqaBPhDJRtpyg\ngPYNDvMR4HjkFz1FjdT7I2bVOm5rxrs1ss4CmnWWc7QPU2NJ0CGxpYwqGKwQe/5X\n3KCLM9SB3GvsFJZHioKGSeS/sTMebZDZaQT7Qjj9S3v8btTBzxSkyXeRtPIM2/7k\nRieLZ17UdAGHee8EzErO5S0vnH88kgrrtdLfxJqpdR7SRkNBHc4wgWI82xhU6PLv\nZkt18SFjAgMBAAECggEABfMrdkOEcoNuEQDlR7TcXTVYw+Dgbrk5gPI6dABIlgDb\nTFxihTQJkskVLKEPJnrV2eksJ2tBctFpEJwG1hksIutZjM5qdE2MpQNkh6ZLqSWQ\n+oySpsrYwnVjJ1fFYmV2RiiP8X9k3fBuO4x8i8pwCLZRB49D1wJs30CtadKHTIAZ\nwdin38ALAa+Q7ocY9oEIgIX7uypCuHEmoPKtgj3NOGh52p4RFs3t1lD4sg6iUmsM\nhjsjtUGASnMbjJ0vgTxLVbDaRf9jSNNoEVSn6iyKzEGLZR4cFZwp+nt4GpCmdIU5\nPCYU8K9wIKHkvxLnMBcfHPGlfphCTDL45cf3SQTuwQKBgQDASZ1v/yN/NxR3r/Ln\n2UHr+rSlaHw4V2mznEK5DvypUsg9lcQwV8ixMiyW8Y/aEchQyc0eXx7P+dtHqcZb\n3KgUOaNjKmEkTNFOy52EFONONaNU+98MImSgHP4c53IpDhJDbGJOBaFhpc//LiSO\nkFUZyOZ13qL7mTg5KM8F0D7BQQKBgQC1ZYn5TNAj2M3k6GnNx3wBiGIPoY/CnliV\nzyeUqSegIaAvK23qnOozfPUBUmz9UlSbNqF6pJTpFb146Xp5jiAgIVpN7x4XBvot\nAKFMqnkQoSVhLWB5e4FLqHF/0wY9yDIjxw444l55VQspjiPiBmNI589fvQO/jkUQ\nC+joefHVowKBgGoLby8n5zyudLQ5Ld4dXBS3U87xG6i61ImAgO+sSz1acSI9qU/7\n6auHfz3ThMEAE5gyYtQAI28RXZRdFg7tVyioTOpQofgyATDSbFE+b8lfHW+t5Gm9\nwf7nXmE0ZyorH3ldma1rv3+pwVb67KBPCw/IUwjoOrxE2NP1JI8RNLrBAoGAC87C\nPnIbklnIfUALsxNrJQZlq7LOktKP3aCQaQLhy3Ck5q0jCISSUiuuHxnockzrqPbT\naBJShyGdJcO87zCrMqw5Hp2UDdesbUV/OmhWXRjAQCUeBIpfjjc2vCVWYKspaF7K\ntDU4BRneEiRofYwA5nwAabD6D3wJTtQXoxpc/ZUCgYB+5UZtmULVRb78Nf3OOcEB\n1kLzgDNcZcscvEc0IQOUOWWadkkJVlCqxIr1v0/rt01iQ2OOhWI8PMwHutUYoWC4\nAwIfcSz5sDCvTSL3TDfaIehkoDE5WTXcGzFybdROHoADkLlInrJUnEavQ4fmhhg8\nw/6eqex+/wYx0OXJtLj19w==\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "integration_test/testdata/echo-server/go.mod",
    "content": "module local\n\ngo 1.24.0\n\nrequire (\n\tgolang.org/x/sync v0.19.0\n\tgolang.org/x/sys v0.41.0\n)\n"
  },
  {
    "path": "integration_test/testdata/echo-server/go.sum",
    "content": "golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\n"
  },
  {
    "path": "integration_test/testdata/echo-server/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"golang.org/x/sync/errgroup\"\n\t\"golang.org/x/sys/unix\"\n)\n\ntype request struct {\n\tURL     string            `json:\"url\"`\n\tHeaders map[string]string `json:\"headers,omitempty\"`\n\tMethod  string            `json:\"method,omitempty\"`\n\tBody    string            `json:\"body,omitempty\"`\n}\n\nfunc main() {\n\terrLog := log.New(os.Stderr, \"\", log.LstdFlags)\n\toutLog := log.New(os.Stdout, \"\", log.LstdFlags)\n\taddr := os.Getenv(\"LISTEN_ADDRESS\")\n\tportsEnv := os.Getenv(\"PORTS\")\n\tif portsEnv == \"\" {\n\t\tportsEnv = os.Getenv(\"PORT\")\n\t}\n\tif portsEnv == \"\" {\n\t\tportsEnv = \"8080:http\"\n\t}\n\tports := strings.Split(portsEnv, \",\")\n\tcertFile, ok := os.LookupEnv(\"CERT_FILE\")\n\tif !ok {\n\t\tcertFile = \"/certs/tls.crt\"\n\t}\n\tkeyFile, ok := os.LookupEnv(\"KEY_FILE\")\n\tif !ok {\n\t\tkeyFile = \"/certs/tls.key\"\n\t}\n\n\tservers := make([]*http.Server, 0, len(ports))\n\tg, ctx := errgroup.WithContext(context.Background())\n\tshutdownCtx, quit := context.WithCancel(ctx)\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/healthz\", func(w http.ResponseWriter, r *http.Request) {\n\t\toutLog.Print(r.URL.Path)\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tmux.HandleFunc(\"/readyz\", func(w http.ResponseWriter, r *http.Request) {\n\t\toutLog.Print(r.URL.Path)\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\tmux.HandleFunc(\"/quit\", func(w http.ResponseWriter, r *http.Request) {\n\t\toutLog.Print(r.URL.Path)\n\t\tw.WriteHeader(http.StatusOK)\n\t\tquit()\n\t})\n\tmux.HandleFunc(\"/forward\", func(w http.ResponseWriter, r *http.Request) {\n\t\toutLog.Print(r.URL.Path)\n\t\tforwardHandler(w, r, outLog, errLog)\n\t})\n\tmux.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\toutLog.Print(r.URL.Path)\n\t\techoHandler(w, r, outLog, errLog)\n\t})\n\n\tfor _, portAndProto := range ports {\n\t\tproto := \"http\"\n\t\tport := portAndProto\n\t\tif pp := strings.SplitN(portAndProto, \":\", 2); len(pp) == 2 {\n\t\t\tport = pp[0]\n\t\t\tproto = pp[1]\n\t\t}\n\t\tvar addrPort string\n\t\tif addr == \"\" {\n\t\t\taddrPort = \":\" + port\n\t\t} else {\n\t\t\taddrPort = net.JoinHostPort(addr, port)\n\t\t}\n\t\toutLog.Printf(\"Echo server listening on %s.\\n\", addrPort)\n\t\tserver := &http.Server{\n\t\t\tAddr: addrPort,\n\t\t\tBaseContext: func(_ net.Listener) context.Context {\n\t\t\t\treturn ctx\n\t\t\t},\n\t\t\tHandler: mux,\n\t\t}\n\t\tprs := new(http.Protocols)\n\t\tprs.SetHTTP1(true)\n\t\tserver.Protocols = prs\n\t\tswitch proto {\n\t\tcase \"http\":\n\t\t\tprs.SetUnencryptedHTTP2(true)\n\t\t\tg.Go(func() error {\n\t\t\t\t_ = server.ListenAndServe()\n\t\t\t\treturn nil\n\t\t\t})\n\t\tcase \"https\":\n\t\t\tprs.SetHTTP2(true)\n\t\t\tg.Go(func() error {\n\t\t\t\t_ = server.ListenAndServeTLS(certFile, keyFile)\n\t\t\t\treturn nil\n\t\t\t})\n\t\tdefault:\n\t\t\terrLog.Fatalf(\"unknown protocol %q\", proto)\n\t\t}\n\t\tservers = append(servers, server)\n\t}\n\n\tg.Go(func() error {\n\t\tsigstop := make(chan os.Signal, 1)\n\t\tsignal.Notify(sigstop, os.Interrupt, unix.SIGTERM)\n\t\tselect {\n\t\tcase <-sigstop:\n\t\tcase <-shutdownCtx.Done():\n\t\t}\n\t\tsdCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 2*time.Second)\n\t\tdefer cancel()\n\t\tvar errs error\n\t\tfor _, server := range servers {\n\t\t\terrs = errors.Join(errs, server.Shutdown(sdCtx))\n\t\t}\n\t\treturn errs\n\t})\n\n\terr := g.Wait()\n\tif err != nil && !errors.Is(err, context.Canceled) {\n\t\terrLog.Fatalf(\"Echo server exited with error: %v\", err)\n\t}\n}\n\nfunc echoHandler(wr http.ResponseWriter, req *http.Request, outLog, errLog *log.Logger) {\n\tdefer req.Body.Close()\n\tswitch req.Method {\n\tcase http.MethodHead:\n\t\treturn\n\tcase http.MethodGet:\n\t// Accepted\n\tdefault:\n\t\twr.WriteHeader(http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\tbf := &bytes.Buffer{}\n\tif tpID, ok := os.LookupEnv(\"TELEPRESENCE_INTERCEPT_ID\"); ok {\n\t\twriteAndLogf(bf, outLog, \"Intercept id %s\\n\", tpID)\n\t\twriteAndLogf(bf, outLog, \"Intercepted container %q\\n\", os.Getenv(\"TELEPRESENCE_CONTAINER\"))\n\t}\n\twriteAndLogf(bf, outLog, \"%s %s %s\\n\\nHost: %s\\n\", req.Proto, req.Method, req.URL, req.Host)\n\tif len(req.Header) > 0 {\n\t\twriteAndLogf(bf, outLog, \"Headers\\n\")\n\t\tfor key, values := range req.Header {\n\t\t\tfor _, value := range values {\n\t\t\t\twriteAndLogf(bf, outLog, \"  %s: %s\\n\", key, value)\n\t\t\t}\n\t\t}\n\t}\n\thost, err := os.Hostname()\n\tif err == nil {\n\t\twriteAndLogf(bf, outLog, \"Request served by %s\\n\", host)\n\t}\n\t_, _ = io.Copy(bf, req.Body)\n\toutLog.Printf(\"%s | http | %d byte(s)\\n\", req.RemoteAddr, bf.Len())\n\thdr := wr.Header()\n\thdr.Set(\"Content-Type\", \"text/plain\")\n\thdr.Set(\"Content-Length\", strconv.Itoa(bf.Len()))\n\t_, err = bf.WriteTo(wr)\n\tif err != nil {\n\t\terrLog.Printf(\"Error serving HTTP: %v\", err)\n\t}\n}\n\nfunc forwardHandler(wr http.ResponseWriter, req *http.Request, outLog, errLog *log.Logger) {\n\tif req.Method != http.MethodPost {\n\t\twr.WriteHeader(http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\tfwReq := &request{}\n\td := json.NewDecoder(req.Body)\n\td.DisallowUnknownFields()\n\terr := d.Decode(fwReq)\n\tif err != nil {\n\t\terrLog.Printf(\"Error parsing request: %v\", err)\n\t\twr.WriteHeader(http.StatusBadRequest)\n\t\treturn\n\t}\n\tvar fwReqBody io.Reader\n\tif fwReq.Body != \"\" {\n\t\tfwReqBody = strings.NewReader(fwReq.Body)\n\t}\n\tif fwReq.Method == \"\" {\n\t\tfwReq.Method = http.MethodGet\n\t}\n\tfwr, err := http.NewRequest(fwReq.Method, os.ExpandEnv(fwReq.URL), fwReqBody)\n\tif err != nil {\n\t\terrLog.Printf(\"Error creating forward request: %v\", err)\n\t\twr.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\tfor k, v := range fwReq.Headers {\n\t\tfwr.Header.Set(k, os.ExpandEnv(v))\n\t}\n\tfwRsp, err := http.DefaultClient.Do(fwr)\n\tif err != nil {\n\t\terrLog.Printf(\"Error forwarding request: %v\", err)\n\t\twr.WriteHeader(http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer fwRsp.Body.Close()\n\n\twrh := wr.Header()\n\tfor key, values := range fwRsp.Header {\n\t\tfor _, value := range values {\n\t\t\twrh.Add(key, value)\n\t\t}\n\t}\n\tif fwRsp.StatusCode != http.StatusOK {\n\t\twr.WriteHeader(fwRsp.StatusCode)\n\t}\n\t_, err = io.Copy(wr, fwRsp.Body)\n\tif err != nil {\n\t\terrLog.Printf(\"Error forwarding response: %v\", err)\n\t}\n}\n\nfunc writeAndLogf(w *bytes.Buffer, outLog *log.Logger, format string, args ...any) {\n\ts := fmt.Sprintf(format, args...)\n\toutLog.Print(s)\n\t_, _ = w.WriteString(s)\n}\n"
  },
  {
    "path": "integration_test/testdata/k8s/client_experiment.yaml",
    "content": "---\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-easy\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-easy\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: telepresence-test-developer\n\n---\nkind: Role\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name:  traffic-manager-connect\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"services\"]\n    verbs: [\"get\", \"list\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: traffic-manager-connect\nsubjects:\n  - kind: ServiceAccount\n    name: telepresence-test-developer\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  name: traffic-manager-connect\n  kind: Role\n"
  },
  {
    "path": "integration_test/testdata/k8s/client_rancher.goyaml",
    "content": "---\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name:  rancher-inspect\n  labels:\n    purpose: tp-cli-testing\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"nodes\"]\n    verbs: [\"get\", \"list\"]\n\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: rancher-inspect-{{ .ManagerNamespace }}\n  labels:\n    purpose: tp-cli-testing\nsubjects:\n  - kind: ServiceAccount\n    name: telepresence-test-developer\n    namespace: {{ .ManagerNamespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  name: rancher-inspect\n  kind: ClusterRole\n"
  },
  {
    "path": "integration_test/testdata/k8s/client_sa.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: telepresence-test-developer\n"
  },
  {
    "path": "integration_test/testdata/k8s/disruption-budget.goyaml",
    "content": "{{- /*gotype: github.com/telepresenceio/telepresence/v2/integration_test/itest.DisruptionBudget*/ -}}---\napiVersion: policy/v1\nkind: PodDisruptionBudget\nmetadata:\n  name: {{.Name}}\nspec:\n{{- if gt .MinAvailable 0 }}\n  minAvailable: {{ .MinAvailable }}\n{{- end }}\n{{- if gt .MaxUnavailable 0 }}\n\tmaxUnavailable: {{ .MaxUnavailable }}\n{{- end }}\n  selector:\n    matchLabels:\n      budget: {{.Name}}\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-argo-rollout.yaml",
    "content": "---\napiVersion: argoproj.io/v1alpha1\nkind: Rollout\nmetadata:\n  name: echo-argo-rollout\n  labels:\n    app: echo-argo-rollout\nspec:\n  replicas: 5\n  strategy:\n    canary:\n      steps:\n        - setWeight: 20\n        - pause: {}\n        - setWeight: 40\n        - pause: {duration: 10}\n        - setWeight: 60\n        - pause: {duration: 10}\n        - setWeight: 80\n        - pause: {duration: 10}\n  revisionHistoryLimit: 2\n  selector:\n    matchLabels:\n      app: echo-argo-rollout\n  template:\n    metadata:\n      labels:\n        app: echo-argo-rollout\n    spec:\n      automountServiceAccountToken: false\n      containers:\n        - name: echo-server\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: http\n              containerPort: 8080\n          env:\n            - name: PORT\n              value: \"8080\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 16Mi\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-argo-rollout\nspec:\n  ports:\n    - port: 80\n      targetPort: http\n      protocol: TCP\n      name: http\n  selector:\n    app: echo-argo-rollout\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-auto-inject.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-auto-inject\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-auto-inject\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-auto-inject\n  labels:\n    app: echo-auto-inject\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-auto-inject\n  template:\n    metadata:\n      annotations:\n        telepresence.io/inject-traffic-agent: enabled\n      labels:\n        app: echo-auto-inject\n    spec:\n      containers:\n        - name: echo-auto-inject\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-both.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-one\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-both\n  ports:\n    - name: one\n      port: 80\n      targetPort: echo-one\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-two\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-both\n  ports:\n    - name: two\n      port: 80\n      targetPort:  echo-two\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-both\n  labels:\n    app: echo-both\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-both\n  template:\n    metadata:\n      labels:\n        app: echo-both\n    spec:\n      containers:\n        - name: echo-one\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: echo-one\n              containerPort: 8080\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n        - name: echo-two\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: echo-two\n              containerPort: 8081\n          env:\n            - name: PORT\n              value: \"8081\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-double-one-unnamed.yaml",
    "content": "# The echo-double-unnamed deployment exposes two unnamed ports, 8080 and 8081 from a single container\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-double-one-unnamed\n  labels:\n    app: echo-double-one-unnamed\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-double-one-unnamed\n  template:\n    metadata:\n      annotations:\n        telepresence.io/inject-container-ports: all\n      labels:\n        app: echo-double-one-unnamed\n    spec:\n      containers:\n        - name: echo-server\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n            - containerPort: 8081\n          env:\n            - name: PORTS\n              value: \"8080,8081\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-easy.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-easy\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-easy\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-easy\"\n  labels:\n    app: echo-easy\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-easy\n  template:\n    metadata:\n      labels:\n        app: echo-easy\n    spec:\n      containers:\n        - name: echo-easy\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-extra-udp.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-extra-udp\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-extra-udp\n  ports:\n    - name: http\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-extra-udp\n  labels:\n    app: echo-extra-udp\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-extra-udp\n  template:\n    metadata:\n      labels:\n        app: echo-extra-udp\n    spec:\n      containers:\n        - name: echo-udp-server\n          image: ghcr.io/telepresenceio/udp-echo:latest\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: PORT\n              value: \"8080\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n        - name: echo-server\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: http\n              containerPort: 8080\n          env:\n            - name: PORT\n              value: \"8080\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-headless.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-headless\nspec:\n  type: ClusterIP\n  clusterIP: None\n  selector:\n    app: echo-headless\n  ports:\n  - name: http\n    port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: echo-headless\n  labels:\n    app: echo-headless\nspec:\n  replicas: 1\n  serviceName: echo-headless\n  selector:\n    matchLabels:\n      app: echo-headless\n  template:\n    metadata:\n      labels:\n        app: echo-headless\n    spec:\n      containers:\n        - name: echo-headless\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-interpolate.yaml",
    "content": "---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: interpolate-config\ndata:\n  SOME_NAME: \"hello\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-interpolate\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-interpolate\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-interpolate\n  labels:\n    app: echo-interpolate\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-interpolate\n  template:\n    metadata:\n      labels:\n        app: echo-interpolate\n    spec:\n      containers:\n        - name: echo-interpolate\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          envFrom:\n            - configMapRef:\n                name: interpolate-config\n          env:\n            - name: OTHER_NAME\n              value: \"hi\"\n          ports:\n            - containerPort: 8080\n              name: http\n          volumeMounts:\n            - mountPath: /var/log/my-volume\n              name: my-volume\n              subPathExpr: $(SOME_NAME)_$(OTHER_NAME)\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n      volumes:\n        - emptyDir: {}\n          name: my-volume\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-manual-inject-deploy.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"manual-inject\"\n  labels:\n    app: manual-inject\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: manual-inject\n  template:\n    metadata:\n      labels:\n        app: manual-inject\n    spec:\n      containers:\n        - name: echo-container\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 80\n          resources: {}\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-manual-inject-svc.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: \"manual-inject\"\nspec:\n  type: ClusterIP\n  selector:\n    app: manual-inject\n  ports:\n    - port: 80\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-min.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: policy/v1\nkind: PodDisruptionBudget\nmetadata:\n  name: echo\nspec:\n  minAvailable: 1\n  selector:\n    matchLabels:\n      app: echo\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo\"\n  labels:\n    app: echo\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo\n  template:\n    metadata:\n      labels:\n        app: echo\n    spec:\n      containers:\n        - name: echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-no-containerport.yaml",
    "content": "# The echo-no-containerport deployment doesn't expose any ports but will reply on port 8080\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-no-containerport\n  labels:\n    app: echo-no-containerport\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-no-containerport\n  template:\n    metadata:\n      labels:\n        app: echo-no-containerport\n    spec:\n      containers:\n        - name: echo-server\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: PORTS\n              value: \"8080,8081\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-no-svc-ann.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-no-svc-ann\n  labels:\n    app: echo-no-svc-ann\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-no-svc-ann\n  template:\n    metadata:\n      labels:\n        app: echo-no-svc-ann\n        budget: telepresence-test\n      annotations:\n        telepresence.io/inject-container-ports: http\n    spec:\n      automountServiceAccountToken: false\n      containers:\n        - name: echo-server\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: http\n              containerPort: 8080\n          env:\n            - name: PORT\n              value: \"8080\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-no-svc.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-no-svc\n  labels:\n    app: echo-no-svc\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-no-svc\n  template:\n    metadata:\n      labels:\n        app: echo-no-svc\n    spec:\n      automountServiceAccountToken: false\n      containers:\n        - name: echo-server\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: http\n              containerPort: 8080\n          env:\n            - name: PORT\n              value: \"8080\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-no-vols.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-no-vols\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-no-vols\n  ports:\n    - name: http\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-no-vols\n  labels:\n    app: echo-no-vols\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-no-vols\n  template:\n    metadata:\n      labels:\n        app: echo-no-vols\n        budget: telepresence-test\n    spec:\n      automountServiceAccountToken: false\n      containers:\n        - name: echo-server\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: http\n              containerPort: 8080\n          env:\n            - name: PORT\n              value: \"8080\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-one.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-one\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-one\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-one\"\n  labels:\n    app: echo-one\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-one\n  template:\n    metadata:\n      labels:\n        app: echo-one\n    spec:\n      containers:\n        - name: echo-one\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-same-target-port.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-stp\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-stp\n  ports:\n    - name: eighty\n      port: 80\n      targetPort: 8080\n    - name: eighty-eighty\n      port: 8080\n      targetPort: 8080\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-stp\n  labels:\n    app: echo-stp\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-stp\n  template:\n    metadata:\n      labels:\n        app: echo-stp\n    spec:\n      containers:\n        - name: echo-stp\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-secondary.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: echo-data\ndata:\n  text: |\n    Hello from echo\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: socat-data\ndata:\n  text: |\n    Hello from socat\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-secondary\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-secondary\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-secondary\"\n  labels:\n    app: echo-secondary\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-secondary\n  template:\n    metadata:\n      labels:\n        app: echo-secondary\n    spec:\n      volumes:\n        - name: echo-data\n          configMap:\n            name: echo-data\n        - name: socat-data\n          configMap:\n            name: socat-data\n      containers:\n        - name: socat\n          image: alpine/socat:latest\n          imagePullPolicy: IfNotPresent\n          args:\n            - \"tcp-listen:9080,fork,reuseaddr\"\n            - \"tcp-connect:localhost:8080\"\n          env:\n            - name: TAG\n              value: socat\n          ports:\n            - containerPort: 9080\n              name: http\n          volumeMounts:\n            - mountPath: \"/usr/share/data\"\n              name: socat-data\n        - name: echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: TAG\n              value: echo-server\n          volumeMounts:\n            - mountPath: \"/usr/share/data\"\n              name: echo-data\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-spring.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-spring\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-spring\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-spring\"\n  labels:\n    app: echo-spring\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-spring\n  template:\n    metadata:\n      annotations:\n        instrumentation.opentelemetry.io/inject-java: \"true\"\n      labels:\n        app: echo-spring\n    spec:\n      containers:\n        - name: echo-spring\n          image: codelev/echo-spring:latest\n          imagePullPolicy: IfNotPresent\n          startupProbe:\n            httpGet:\n              path: /rest/echo\n              port: 9000\n            initialDelaySeconds: 8\n            periodSeconds: 3\n            failureThreshold: 10\n          ports:\n            - containerPort: 9000\n              name: http\n          resources:\n            limits:\n              cpu: 500m\n              memory: 1024Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-tls.goyaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Name }}\n  ports:\n    - name: http\n      port: 80\n      targetPort: http\n    - name: https\n      port: 443\n      targetPort: https\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Name }}\n  labels:\n    app: {{ .Name }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: {{ .Name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Name }}\n{{- with .Annotations }}\n      annotations:\n  {{- toYaml . | nindent 8 }}\n{{- end}}\n    spec:\n      serviceAccountName: {{ .ServiceAccount }}\n      containers:\n        - name: backend\n          image: \"{{ .Registry }}/{{ .Image }}\"\n          ports:\n            - name: http\n              containerPort: 8080\n            - name: https\n              containerPort: 8443\n{{- with .Environment }}\n          env:\n  {{- toYaml . | nindent 12 }}\n{{- end}}\n\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-udp-tcp-unnamed.yaml",
    "content": "# The echo-double-unnamed deployment exposes two unnamed ports, 8080 and 8081 from a single container\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-udp-tcp-unnamed\n  labels:\n    app: echo-udp-tcp-unnamed\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-udp-tcp-unnamed\n  template:\n    metadata:\n      labels:\n        app: echo-udp-tcp-unnamed\n    spec:\n      containers:\n        - name: echo-udp-server\n          image: ghcr.io/telepresenceio/udp-echo:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              protocol: UDP\n          env:\n            - name: PORT\n              value: \"8080\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n        - name: echo-server\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n          env:\n            - name: PORT\n              value: \"8080\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 8Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-w-hostalias.goyaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo\nspec:\n  type: ClusterIP\n  selector:\n    app: echo\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo\n  labels:\n    app: echo\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo\n  template:\n    metadata:\n      labels:\n        app: echo\n    spec:\n      hostAliases:\n        - ip: \"{{ .AliasIP }}\"\n          hostnames:\n            {{- range .Aliases }}\n            - {{ . }}\n            {{- end }}\n      containers:\n        - name: echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-w-sidecars.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-w-sidecars\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-w-sidecars\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-w-sidecars\n  labels:\n    app: echo-w-sidecars\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-w-sidecars\n  template:\n    metadata:\n      labels:\n        app: echo-w-sidecars\n    spec:\n      containers:\n        - name: echo-main\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n        - name: echo-side-one\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8081\n              name: http-one\n          env:\n            - name: PORT\n              value: \"8081\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n        - name: echo-side-two\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8082\n              name: http-two\n          env:\n            - name: PORT\n              value: \"8082\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n        - name: echo-side-three\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8083\n              name: http-three\n          env:\n            - name: PORT\n              value: \"8083\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo-w-subdomain.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: subsonic\nspec:\n  selector:\n    app: subsonic\n  clusterIP: None\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: echo-subsonic\n  labels:\n    app: subsonic\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: subsonic\n  template:\n    metadata:\n      labels:\n        app: subsonic\n    spec:\n      hostname: echo\n      subdomain: subsonic\n      containers:\n        - name: echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/echo_with_env.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-easy\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-easy\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-easy\"\n  labels:\n    app: echo-easy\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-easy\n  template:\n    metadata:\n      labels:\n        app: echo-easy\n    spec:\n      containers:\n        - name: echo-easy\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: TEST\n              value: \"DATA\"\n            - name: INTERCEPT\n              value: \"ENV\"\n            - name: DATABASE_HOST\n              value: \"HOST_NAME\"\n            - name: DATABASE_PASSWORD\n              value: \"SUPER_SECRET_PASSWORD\"\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/generic.goyaml",
    "content": "{{- /*gotype: github.com/telepresenceio/telepresence/v2/integration_test/itest.Generic*/ -}}\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ .Name }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .Name }}\n  ports:\n{{- range .ServicePorts }}\n    - port: {{ .Number }}\n{{- with .Name }}\n      name: {{ . }}\n{{- end}}\n{{- with .TargetPort }}\n      targetPort: {{ . }}\n{{- end}}\n{{- with .Protocol }}\n      protocol: {{ . }}\n{{- end}}\n{{- else }}\n    - port: 80\n      name: http\n      targetPort: {{ .TargetPort | default \"http\" }}\n{{- end}}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ .Name }}\n  labels:\n    app: {{ .Name }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: {{ .Name }}\n  template:\n    metadata:\n      labels:\n        app: {{ .Name }}\n{{- with .Labels }}\n  {{- toYaml . | nindent 8 }}\n{{- end}}\n{{- with .Annotations }}\n      annotations:\n  {{- toYaml . | nindent 8 }}\n{{- end}}\n    spec:\n{{- with .Volumes }}\n      volumes:\n  {{- toYaml . | nindent 8 }}\n{{- end}}\n      containers:\n        - name: backend\n{{- if .Registry }}\n          image: {{ .Registry }}/{{ .Image }}\n{{- else }}\n          image: {{ .Image }}\n{{- end}}\n          imagePullPolicy: Always\n          ports:\n{{- range .ContainerPorts }}\n            - containerPort: {{ .Number }}\n  {{- with .Name }}\n              name: {{ . }}\n  {{- end}}\n  {{- with .Protocol }}\n              protocol: {{ . }}\n  {{- end}}\n{{- else }}\n            - containerPort: {{ .ContainerPort | default 8080 }}\n              name: http\n{{- end }}\n{{- with .VolumeMounts }}\n          volumeMounts:\n  {{- toYaml . | nindent 10 }}\n{{- end }}\n{{- with .Environment }}\n          env:\n{{- toYaml . | nindent 12 }}\n{{- end}}\n\n"
  },
  {
    "path": "integration_test/testdata/k8s/hello-w-volumes.goyaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: nginx\ndata:\n  default.conf.template: |\n    server {\n        listen       ${NGINX_PORT};\n        server_name  localhost;\n\n        location / {\n            root   /usr/share/nginx/html;\n            index  index.html index.htm;\n        }\n\n        error_page   500 502 503 504  /50x.html;\n        location = /50x.html {\n            root   /usr/share/nginx/html;\n        }\n    }\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: hello-data-1\ndata:\n  index.html: |\n    <html>\n      <body>\n        <p id=\"hello\">Hello from volume 1!</p>\n       </body>\n    </html>\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: hello-data-2\ndata:\n  index.html: |\n    <html>\n      <body>\n        <p id=\"hello\">Hello from volume 2!</p>\n      </body>\n    </html>\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: hello-secret-1\ntype: kubernetes.io/basic-auth\nstringData:\n  username: \"hello-1\"\n  password: \"hello-1\"\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: hello-secret-2\ntype: kubernetes.io/basic-auth\nstringData:\n  username: \"hello-2\"\n  password: \"hello-2\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: hello\nspec:\n  type: ClusterIP\n  selector:\n    app: hello\n  ports:\n    - name: http\n      port: 80\n      targetPort: http\n    - name: http2\n      port: 81\n      appProtocol: http\n      targetPort: http2\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: hello\n  labels:\n    app: hello\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: hello\n  template:\n    metadata:\n      labels:\n        app: hello\n{{- with .Annotations }}\n      annotations:\n  {{- toYaml . | nindent 8 }}\n{{- end}}\n    spec:\n      volumes:\n        - name: hello-data-volume-1\n          configMap:\n            name: hello-data-1\n        - name: hello-data-volume-2\n          configMap:\n            name: hello-data-2\n        - name: hello-secret-volume-1\n          secret:\n            secretName: hello-secret-1\n        - name: hello-secret-volume-2\n          secret:\n            secretName: hello-secret-2\n        - name: nginx-config\n          configMap:\n            name: nginx\n      containers:\n        - name: hello-container-1\n          image: nginx\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: NGINX_HOST\n              value: \"hello-1.com\"\n            - name: NGINX_PORT\n              value: \"80\"\n          ports:\n            - containerPort: 80\n              name: http\n          volumeMounts:\n            - mountPath: \"/usr/share/nginx/html\"\n              name: hello-data-volume-1\n            - mountPath: \"/var/run/secrets/datawire.io/auth\"\n              name: hello-secret-volume-1\n            - mountPath: /etc/nginx/templates/\n              name: nginx-config\n          livenessProbe:\n            httpGet:\n              path: /\n              port: http\n            initialDelaySeconds: 3\n            periodSeconds: 3\n        - name: hello-container-2\n          image: nginx\n          imagePullPolicy: IfNotPresent\n          env:\n            - name: NGINX_HOST\n              value: \"hello-2.com\"\n            - name: NGINX_PORT\n              value: \"81\"\n          ports:\n            - containerPort: 81\n              name: http2\n          volumeMounts:\n            - mountPath: \"/usr/share/nginx/html\"\n              name: hello-data-volume-2\n            - mountPath: \"/var/run/secrets/datawire.io/auth\"\n              name: hello-secret-volume-2\n            - mountPath: /etc/nginx/templates/\n              name: nginx-config\n"
  },
  {
    "path": "integration_test/testdata/k8s/many-volumes.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cm1\ndata:\n  data.txt: |\n    Some data 1\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cm2\ndata:\n  data.txt: |\n    Some data 2\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cm3\ndata:\n  data.txt: |\n    Some data 3\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cm4\ndata:\n  data.txt: |\n    Some data 4\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cm5\ndata:\n  data.txt: |\n    Some data 5\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cm6\ndata:\n  data.txt: |\n    Some data 6\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cm7\ndata:\n  data.txt: |\n    Some data 7\n---\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: cm8\ndata:\n  data.txt: |\n    Some data 8\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: hello-secret-1\ntype: kubernetes.io/basic-auth\nstringData:\n  username: \"hello-1\"\n  password: \"hello-1\"\n---\napiVersion: v1\nkind: Secret\nmetadata:\n  name: hello-secret-2\ntype: kubernetes.io/basic-auth\nstringData:\n  username: \"hello-2\"\n  password: \"hello-2\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: hello\nspec:\n  type: ClusterIP\n  selector:\n    app: hello\n  ports:\n    - name: http\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: hello\n  labels:\n    app: hello\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: hello\n  template:\n    metadata:\n      labels:\n        app: hello\n    spec:\n      volumes:\n        - name: v1\n          configMap:\n            name: cm1\n        - name: v2\n          configMap:\n            name: cm2\n        - name: v3\n          configMap:\n            name: cm3\n        - name: v4\n          configMap:\n            name: cm4\n        - name: v5\n          configMap:\n            name: cm5\n        - name: v6\n          configMap:\n            name: cm6\n        - name: v7\n          configMap:\n            name: cm7\n        - name: v8\n          configMap:\n            name: cm8\n        - name: ed1\n          emptyDir: {}\n        - name: ed2\n          emptyDir: {}\n        - name: ed3\n          emptyDir: {}\n        - name: ed4\n          emptyDir: {}\n        - name: ed5\n          emptyDir: {}\n        - name: ed6\n          emptyDir: {}\n        - name: ed7\n          emptyDir: {}\n        - name: ed8\n          emptyDir: {}\n      containers:\n        - name: hello\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          volumeMounts:\n            - mountPath: \"/usr/share/cm1\"\n              name: v1\n            - mountPath: \"/usr/share/cm2\"\n              name: v2\n            - mountPath: \"/usr/share/cm3\"\n              name: v3\n            - mountPath: \"/usr/share/cm4\"\n              name: v4\n            - mountPath: \"/usr/share/cm5\"\n              name: v5\n            - mountPath: \"/usr/share/cm6\"\n              name: v6\n            - mountPath: \"/usr/share/cm7\"\n              name: v7\n            - mountPath: \"/usr/share/cm8\"\n              name: v8\n            - mountPath: \"/tmp1\"\n              name: ed1\n            - mountPath: \"/tmp2\"\n              name: ed2\n            - mountPath: \"/tmp3\"\n              name: ed3\n            - mountPath: \"/tmp4\"\n              name: ed4\n            - mountPath: \"/tmp5\"\n              name: ed5\n            - mountPath: \"/tmp6\"\n              name: ed6\n            - mountPath: \"/tmp7\"\n              name: ed7\n            - mountPath: \"/tmp8\"\n              name: ed8\n"
  },
  {
    "path": "integration_test/testdata/k8s/memory-constraints.yaml",
    "content": "apiVersion: v1\nkind: LimitRange\nmetadata:\n  name: mem-min-max-test\nspec:\n  limits:\n  - max:\n      memory: 100Mi\n    min:\n      memory: 20Mi\n    type: Container\n\n"
  },
  {
    "path": "integration_test/testdata/k8s/namespaces.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: alpha\n  labels:\n    phase: a1\n    scope: dev\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: beta\n  labels:\n    phase: a1\n    scope: dev\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: alpha-test\n  labels:\n    phase: none\n    scope: unscoped\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: alpha-rc\n  labels:\n    phase: a2\n    scope: dev\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: beta-rc\n  labels:\n    phase: a2\n    scope: dev\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: alpha-live\n  labels:\n    phase: ga\n    scope: prod\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: beta-live\n  labels:\n    phase: ga\n    scope: prod\n"
  },
  {
    "path": "integration_test/testdata/k8s/pol-disabled.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: pol-disabled\nspec:\n  type: ClusterIP\n  selector:\n    app: pol-disabled\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: pol-disabled\n  labels:\n    app: pol-disabled\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: pol-disabled\n  template:\n    metadata:\n      annotations:\n        telepresence.io/inject-traffic-agent: disabled\n      labels:\n        app: pol-disabled\n    spec:\n      containers:\n        - name: echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/pol-enabled.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: pol-enabled\nspec:\n  type: ClusterIP\n  selector:\n    app: pol-enabled\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: pol-enabled\n  labels:\n    app: pol-enabled\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: pol-enabled\n  template:\n    metadata:\n      annotations:\n        telepresence.io/inject-traffic-agent: enabled\n      labels:\n        app: pol-enabled\n    spec:\n      containers:\n        - name: echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/pol-none.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: pol-none\nspec:\n  type: ClusterIP\n  selector:\n    app: pol-none\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: pol-none\n  labels:\n    app: pol-none\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: pol-none\n  template:\n    metadata:\n       labels:\n        app: pol-none\n    spec:\n      containers:\n        - name: echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/pv.goyaml",
    "content": "{{- /*gotype: github.com/telepresenceio/telepresence/v2/integration_test/itest.PersistentVolume*/ -}}\napiVersion: v1\nkind: PersistentVolume\nmetadata:\n  name: {{ .Name }}\n  labels:\n    purpose: tp-cli-testing\n{{- with .Annotations }}\n  annotations:\n  {{- toYaml . | nindent 4 }}\n{{- end }}\nspec:\n  nodeAffinity:\n    required:\n      nodeSelectorTerms:\n        - matchExpressions:\n          - key: kubernetes.io/os\n            operator: In\n            values:\n            - linux\n  capacity:\n    storage: 0.1Gi\n  accessModes:\n  - ReadWriteMany\n  persistentVolumeReclaimPolicy: Retain\n  hostPath:\n    path: /tmp/{{ .Name }}-pv\n{{- with .StorageClassName }}\n  storageClassName: {{ . }}\n{{- end }}\n"
  },
  {
    "path": "integration_test/testdata/k8s/pvc.goyaml",
    "content": "{{- /*gotype: github.com/telepresenceio/telepresence/v2/integration_test/itest.PersistentVolumeClaim*/ -}}\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  name: {{ .Name }}\n  labels:\n    purpose: tp-cli-testing\n{{- with .Annotations }}\n  annotations:\n  {{- toYaml . | nindent 4 }}\n{{- end }}\nspec:\n  accessModes:\n    - ReadWriteMany\n  resources:\n    requests:\n      storage: 0.1Gi\n{{- with .StorageClassName }}\n  storageClassName: {{ . }}\n{{- end }}\n"
  },
  {
    "path": "integration_test/testdata/k8s/rs-echo.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: rs-echo\nspec:\n  type: ClusterIP\n  selector:\n    app: rs-echo\n  ports:\n    - name: http\n      port: 80\n      targetPort: 8080\n---\napiVersion: apps/v1\nkind: ReplicaSet\nmetadata:\n  name: rs-echo\n  labels:\n    app: rs-echo\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: rs-echo\n  template:\n    metadata:\n      labels:\n        app: rs-echo\n        budget: telepresence-test\n    spec:\n      containers:\n        - name: rs-echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - name: http\n              containerPort: 8080\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/secret-reader-rbac.goyaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: secret-reader\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"secrets\"]\n    verbs: [\"get\", \"watch\", \"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: read-secrets\nsubjects:\n  - kind: ServiceAccount\n    name: {{ .Name }}\n    apiGroup: \"\"\nroleRef:\n  kind: Role\n  name: secret-reader\n  apiGroup: rbac.authorization.k8s.io\n"
  },
  {
    "path": "integration_test/testdata/k8s/ss-echo.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: ss-echo\nspec:\n  type: ClusterIP\n  selector:\n    app: ss-echo\n  ports:\n    - name: http\n      port: 80\n      targetPort: 8080\n---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: ss-echo\n  labels:\n    app: ss-echo\nspec:\n  serviceName: \"ss-echo\"\n  replicas: 2\n  selector:\n    matchLabels:\n      app: ss-echo\n  template:\n    metadata:\n      labels:\n        app: ss-echo\n        budget: telepresence-test\n    spec:\n      containers:\n        - name: ss-echo\n          image: ghcr.io/telepresenceio/echo-server:latest\n          imagePullPolicy: IfNotPresent\n          ports:\n            - containerPort: 8080\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/svc-deploy.goyaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: {{ default .AppName .ServiceName }}\n  labels:\n    app: {{ .AppName }}\nspec:\n  type: ClusterIP\n  selector:\n    app: {{ .AppName }}\n  ports:\n  {{- range .Ports }}\n    - port: {{ .ServicePortNumber }}\n    {{- if .ServicePortName }}\n      name: {{ .ServicePortName }}\n    {{- end }}\n    {{- if .AppProtocol }}\n      appProtocol: {{ .AppProtocol }}\n    {{- end }}\n      protocol: {{ default \"TCP\" .Protocol }}\n      targetPort: {{ default .TargetPortNumber .TargetPortName }}\n  {{- end }}\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ default .AppName .DeploymentName }}\n  labels:\n    app: {{ .AppName }}\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: {{ .AppName }}\n  template:\n    metadata:\n      labels:\n        app: {{ .AppName }}\n    spec:\n      containers:\n        - name: {{ default .AppName .ContainerName }}\n          image: {{ default \"ghcr.io/telepresenceio/echo-server:latest\" .Image }}\n          imagePullPolicy: {{ default \"IfNotPresent\" .PullPolicy }}\n          env:\n          {{- range $key, $value := .Env }}\n            - name: {{  $key }}\n              value: {{ quote $value }}\n          {{- end }}\n          ports:\n          {{- range .Ports }}\n            - containerPort: {{ .TargetPortNumber }}\n            {{- if .TargetPortName }}\n              name: {{ .TargetPortName }}\n            {{- end }}\n              protocol: {{ default \"TCP\" .Protocol }}\n          {{- end }}\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "integration_test/testdata/k8s/tel-cert.yaml",
    "content": "apiVersion: v1\nkind: Secret\ntype: kubernetes.io/tls\nmetadata:\n  name: tel-cert\ndata:\n  tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURaVENDQWsyZ0F3SUJBZ0lVUnd4WVMyNnl1bktJNk9jSmg0bG9ORFh2Z3VNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd05ERVlNQllHQTFVRUF3d1BkR1ZzWlhCeVpYTmxibU5sTG1sdk1SZ3dGZ1lEVlFRS0RBOTBaV3hsY0hKbApjMlZ1WTJVdWFXOHdIaGNOTWpVd09USTNNREV6T0RFd1doY05Nall3T1RJM01ERXpPREV3V2pBME1SZ3dGZ1lEClZRUUREQTkwWld4bGNISmxjMlZ1WTJVdWFXOHhHREFXQmdOVkJBb01EM1JsYkdWd2NtVnpaVzVqWlM1cGJ6Q0MKQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFJaEFVUDN6SVFUbVh0YkpXQXYvd1g5dQoxaUZWVFMvMXR5OHBWZFMzeVBXbmdaYmQzU0dEZHRIa20wTGlzL1BEQmlQMklXSGpIUVA4WUMzMzJKM3ZnaUgyCm5TVEp4WEFsSnowa3BKeVQwWjFOVjNkMUl0KzFRQm5oNUZGTjVYdlpZWjZwb0UrRU1sRzJuS0NBOWcwTzh4SGcKZU9RWFBVV04xUHNqWnRVNmJtdkd1eld5emdLYWRaWnp0QTlUWTBuUUliR2xqQ29ZckJCNy9sZmNvSXN6MUlIYwphK3dVbGtlS2dvWko1TCt4TXg1dGtObHBCUHRDT1AxTGUveHUxTUhQRktUSmQ1RzA4Z3piL3VSR0o0dG5YdFIwCkFZZDU3d1RNU3M3bExTK2NmenlTQ3V1MTB0L0VtcWwxSHRKR1EwRWR6akNCWWp6YkdGVG84dTltUzNYeElXTUMKQXdFQUFhTnZNRzB3SFFZRFZSME9CQllFRk1mZ3B1SHhRRjQ1S08yWXNPc1p6MUtzeXRsQU1COEdBMVVkSXdRWQpNQmFBRk1mZ3B1SHhRRjQ1S08yWXNPc1p6MUtzeXRsQU1BOEdBMVVkRXdFQi93UUZNQU1CQWY4d0dnWURWUjBSCkJCTXdFWUlQZEdWc1pYQnlaWE5sYm1ObExtbHZNQTBHQ1NxR1NJYjNEUUVCQ3dVQUE0SUJBUUJleUgvRURzQjYKN2s1d1ZqdUR3bys0VVNSakoxc0Y2aHZ4QkJ4c1Jwd2ljdDg0RXJNM0ZhUUsvdi9KZi9oZ3R2Rm1UVGxNbjNFTQp2dXpRRWdzTHNGWGxDY3QyRjdJNkY1eXdJQ0NTdE1MZnp2QzE3bFU4L3Y0STBzZURTN0ZlUXp4MTMzdVlDQTlpClBKcktqK0Q2QXkrcHAwc2w2dmtVbEhWbTlGaUQrdGFNWThlV1QyRnZRU2c2bjJnMlVFeU9BOUx3V0EwS2FnQmsKRUMxdmtkK3c3RlpTTXFGSSt2ZGVqKzI1RVlxekF1QzZDdzFyYlduVVJoS3A4K0dpVVM4akxqRmFTcGVKbXVQcAo3TjRtbFdyOXQzQWF0WXRla2pobnFGWElrdFFTOWluS2UrRjRoMTFYaWVIQ3pNM2p5RHZDRkZHczV0V1ZWNC9HCmM3YUErWmZKL0dYWQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n  tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktZd2dnU2lBZ0VBQW9JQkFRQ0lRRkQ5OHlFRTVsN1cKeVZnTC84Ri9idFloVlUwdjliY3ZLVlhVdDhqMXA0R1czZDBoZzNiUjVKdEM0clB6d3dZajlpRmg0eDBEL0dBdAo5OWlkNzRJaDlwMGt5Y1Z3SlNjOUpLU2NrOUdkVFZkM2RTTGZ0VUFaNGVSUlRlVjcyV0dlcWFCUGhESlJ0cHlnCmdQWU5Edk1SNEhqa0Z6MUZqZFQ3STJiVk9tNXJ4cnMxc3M0Q21uV1djN1FQVTJOSjBDR3hwWXdxR0t3UWUvNVgKM0tDTE05U0IzR3ZzRkpaSGlvS0dTZVMvc1RNZWJaRFphUVQ3UWpqOVMzdjhidFRCenhTa3lYZVJ0UElNMi83awpSaWVMWjE3VWRBR0hlZThFekVyTzVTMHZuSDg4a2dycnRkTGZ4SnFwZFI3U1JrTkJIYzR3Z1dJODJ4aFU2UEx2ClprdDE4U0ZqQWdNQkFBRUNnZ0VBQmZNcmRrT0Vjb051RVFEbFI3VGNYVFZZdytEZ2JyazVnUEk2ZEFCSWxnRGIKVEZ4aWhUUUprc2tWTEtFUEpuclYyZWtzSjJ0QmN0RnBFSndHMWhrc0l1dFpqTTVxZEUyTXBRTmtoNlpMcVNXUQorb3lTcHNyWXduVmpKMWZGWW1WMlJpaVA4WDlrM2ZCdU80eDhpOHB3Q0xaUkI0OUQxd0pzMzBDdGFkS0hUSUFaCndkaW4zOEFMQWErUTdvY1k5b0VJZ0lYN3V5cEN1SEVtb1BLdGdqM05PR2g1MnA0UkZzM3QxbEQ0c2c2aVVtc00KaGpzanRVR0FTbk1iakowdmdUeExWYkRhUmY5alNOTm9FVlNuNml5S3pFR0xaUjRjRlp3cCtudDRHcENtZElVNQpQQ1lVOEs5d0lLSGt2eExuTUJjZkhQR2xmcGhDVERMNDVjZjNTUVR1d1FLQmdRREFTWjF2L3lOL054UjNyL0xuCjJVSHIrclNsYUh3NFYybXpuRUs1RHZ5cFVzZzlsY1F3VjhpeE1peVc4WS9hRWNoUXljMGVYeDdQK2R0SHFjWmIKM0tnVU9hTmpLbUVrVE5GT3k1MkVGT05PTmFOVSs5OE1JbVNnSFA0YzUzSXBEaEpEYkdKT0JhRmhwYy8vTGlTTwprRlVaeU9aMTNxTDdtVGc1S004RjBEN0JRUUtCZ1FDMVpZbjVUTkFqMk0zazZHbk54M3dCaUdJUG9ZL0NubGlWCnp5ZVVxU2VnSWFBdksyM3FuT296ZlBVQlVtejlVbFNiTnFGNnBKVHBGYjE0NlhwNWppQWdJVnBON3g0WEJ2b3QKQUtGTXFua1FvU1ZoTFdCNWU0RkxxSEYvMHdZOXlESWp4dzQ0NGw1NVZRc3BqaVBpQm1OSTU4OWZ2UU8vamtVUQpDK2pvZWZIVm93S0JnR29MYnk4bjV6eXVkTFE1TGQ0ZFhCUzNVODd4RzZpNjFJbUFnTytzU3oxYWNTSTlxVS83CjZhdUhmejNUaE1FQUU1Z3lZdFFBSTI4UlhaUmRGZzd0Vnlpb1RPcFFvZmd5QVREU2JGRStiOGxmSFcrdDVHbTkKd2Y3blhtRTBaeW9ySDNsZG1hMXJ2Mytwd1ZiNjdLQlBDdy9JVXdqb09yeEUyTlAxSkk4Uk5MckJBb0dBQzg3QwpQbklia2xuSWZVQUxzeE5ySlFabHE3TE9rdEtQM2FDUWFRTGh5M0NrNXEwakNJU1NVaXV1SHhub2NrenJxUGJUCmFCSlNoeUdkSmNPODd6Q3JNcXc1SHAyVURkZXNiVVYvT21oV1hSakFRQ1VlQklwZmpqYzJ2Q1ZXWUtzcGFGN0sKdERVNEJSbmVFaVJvZll3QTVud0FhYkQ2RDN3SlR0UVhveHBjL1pVQ2dZQis1VVp0bVVMVlJiNzhOZjNPT2NFQgoxa0x6Z0ROY1pjc2N2RWMwSVFPVU9XV2Fka2tKVmxDcXhJcjF2MC9ydDAxaVEyT09oV0k4UE13SHV0VVlvV0M0CkF3SWZjU3o1c0RDdlRTTDNURGZhSWVoa29ERTVXVFhjR3pGeWJkUk9Ib0FEa0xsSW5ySlVuRWF2UTRmbWhoZzgKdy82ZXFleCsvd1l4ME9YSnRMajE5dz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K\n"
  },
  {
    "path": "integration_test/testdata/k8s/with-probes.yaml",
    "content": "---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: with-probes\nspec:\n  replicas: 2\n  selector:\n    matchLabels:\n      app: with-probes\n  template:\n    metadata:\n      annotations:\n        consul.hashicorp.com/connect-inject: 'false'\n        sidecar.istio.io/inject: 'false'\n      labels:\n        app: with-probes\n        budget: telepresence-test\n    spec:\n      containers:\n        - name: sample-app\n          image: gcr.io/datawire/k8s-initializer-sample-app:latest\n          imagePullPolicy: Always\n          env:\n            - name: LISTEN_PORT\n              value: '3000'\n          livenessProbe:\n            httpGet:\n              path: /health\n              port: http\n            periodSeconds: 10\n          readinessProbe:\n            httpGet:\n              path: /health\n              port: http\n            periodSeconds: 10\n          ports:\n            - containerPort: 3000\n              name: http\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: with-probes\n  labels:\n    app: with-probes\nspec:\n  ports:\n    - port: 80\n      name: http\n      targetPort: http\n  selector:\n    app: with-probes\n"
  },
  {
    "path": "integration_test/testdata/k8screds/main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"k8s.io/client-go/pkg/apis/clientauthentication\"\n\t\"k8s.io/client-go/pkg/apis/clientauthentication/install\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\nfunc main() {\n\tif err := run(os.Args); err != nil {\n\t\t_, _ = fmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n}\n\nfunc run(args []string) error {\n\tvar cn string\n\tvar fm map[string]string\n\tswitch len(args) {\n\tcase 1:\n\tcase 2:\n\t\tcn = os.Args[1]\n\t\tfm = map[string]string{\"context\": cn}\n\tdefault:\n\t\treturn fmt.Errorf(\"usage %s <name of kubecontext>\", args)\n\t}\n\tflags, err := k8s.ConfigFlags(fm)\n\tif err != nil {\n\t\treturn err\n\t}\n\tconfig, err := flags.ToRawKubeConfigLoader().RawConfig()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif cn == \"\" {\n\t\tcn = config.CurrentContext\n\t} else {\n\t\tconfig.CurrentContext = cn\n\t}\n\tif err = api.MinifyConfig(&config); err != nil {\n\t\treturn fmt.Errorf(\"unable to load context %q: %w\", cn, err)\n\t}\n\t// Ensure that all certs are embedded instead of reachable using a path\n\tif err = api.FlattenConfig(&config); err != nil {\n\t\treturn fmt.Errorf(\"unable to flatten context %q: %w\", cn, err)\n\t}\n\tcc := config.Contexts[cn]\n\tai, ok := config.AuthInfos[cc.AuthInfo]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unable to load authinfo %q for context %q\", cc.AuthInfo, cn)\n\t}\n\n\tvar data []byte\n\tif ec := ai.Exec; ec != nil {\n\t\tdata, err = resolveExec(ec)\n\t} else {\n\t\tdata, err = resolveCreds(ai, config.Clusters[cc.Cluster])\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = os.Stdout.Write(data)\n\treturn err\n}\n\nfunc resolveCreds(ai *api.AuthInfo, cl *api.Cluster) ([]byte, error) {\n\tst := clientauthentication.ExecCredentialStatus{\n\t\tToken: ai.Token,\n\t}\n\tif len(ai.ClientCertificateData) > 0 {\n\t\tst.ClientCertificateData = string(ai.ClientCertificateData)\n\t}\n\tif len(ai.ClientKeyData) > 0 {\n\t\tst.ClientKeyData = string(ai.ClientKeyData)\n\t}\n\tcreds := clientauthentication.ExecCredential{\n\t\tTypeMeta: meta.TypeMeta{\n\t\t\tKind:       \"ExecCredential\",\n\t\t\tAPIVersion: \"client.authentication.k8s.io/v1beta1\",\n\t\t},\n\t\tSpec: clientauthentication.ExecCredentialSpec{\n\t\t\tInteractive: false,\n\t\t},\n\t\tStatus: &st,\n\t}\n\tif cl != nil {\n\t\tcreds.Spec.Cluster = &clientauthentication.Cluster{\n\t\t\tServer:                   cl.Server,\n\t\t\tTLSServerName:            cl.TLSServerName,\n\t\t\tInsecureSkipTLSVerify:    cl.InsecureSkipTLSVerify,\n\t\t\tCertificateAuthorityData: cl.CertificateAuthorityData,\n\t\t\tProxyURL:                 cl.ProxyURL,\n\t\t\tDisableCompression:       cl.DisableCompression,\n\t\t}\n\t}\n\tscheme := runtime.NewScheme()\n\tinstall.Install(scheme)\n\tcodecs := serializer.NewCodecFactory(scheme)\n\treturn runtime.Encode(codecs.LegacyCodec(creds.GroupVersionKind().GroupVersion()), &creds)\n}\n\nfunc resolveExec(execConfig *api.ExecConfig) ([]byte, error) {\n\tvar buf bytes.Buffer\n\tcmd := exec.Command(execConfig.Command, execConfig.Args...)\n\tcmd.Stdout = &buf\n\tcmd.Stderr = os.Stderr\n\tcmd.Env = os.Environ()\n\tif len(execConfig.Env) > 0 {\n\t\tem := dos.FromEnvPairs(cmd.Env)\n\t\tfor _, ev := range execConfig.Env {\n\t\t\tem[ev.Name] = ev.Value\n\t\t}\n\t\tcmd.Env = em.Environ()\n\t}\n\n\tif err := cmd.Run(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to run host command: %w\", err)\n\t}\n\n\treturn buf.Bytes(), nil\n}\n"
  },
  {
    "path": "integration_test/testdata/otel/helm-yamls/otel-operator.yml",
    "content": "# Default values for opentelemetry-operator.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 1\n\n## Provide a name in place of opentelemetry-operator (includes the chart's release name).\n##\nnameOverride: \"\"\n\n## Fully override the name (excludes the chart's release name).\n##\nfullnameOverride: \"\"\n\n## Reference one or more secrets to be used when pulling images from authenticated repositories.\nimagePullSecrets: []\n\n## Kubernetes cluster domain suffix\nclusterDomain: cluster.local\n\n# Common labels to add to all otel-operator resources. Evaluated as a template.\nadditionalLabels: {}\n\n## Pod Disruption Budget configuration\n##\npdb:\n  ## Enable/disable a Pod Disruption Budget creation\n  ##\n  create: false\n  ## Minimum number/percentage of pods that should remain scheduled\n  ##\n  minAvailable: 1\n  ## Maximum number/percentage of pods that may be made unavailable\n  ##\n  maxUnavailable: \"\"\n\n## Provide OpenTelemetry Operator manager container image and resources.\n##\nmanager:\n  image:\n    repository: ghcr.io/open-telemetry/opentelemetry-operator/opentelemetry-operator\n    tag: 0.112.0\n  collectorImage:\n    repository: \"\"\n    tag: 0.120.0\n  opampBridgeImage:\n    repository: \"\"\n    tag: \"\"\n  targetAllocatorImage:\n    repository: \"\"\n    tag: \"\"\n  autoInstrumentationImage:\n    java:\n      repository: \"\"\n      tag: \"\"\n    nodejs:\n      repository: \"\"\n      tag: \"\"\n    python:\n      repository: \"\"\n      tag: \"\"\n    dotnet:\n      repository: \"\"\n      tag: \"\"\n    apacheHttpd:\n      repository: \"\"\n      tag: \"\"\n    # The Go instrumentation support in the operator is disabled by default.\n    # To enable it, use the operator.autoinstrumentation.go feature gate.\n    go:\n      repository: \"\"\n      tag: \"\"\n  # Feature Gates are a comma-delimited list of feature gate identifiers.\n  # Prefix a gate with '-' to disable support.\n  # Prefixing a gate with '+' or no prefix will enable support.\n  # A full list of valid identifiers can be found here: https://github.com/open-telemetry/opentelemetry-operator/blob/main/pkg/featuregate/featuregate.go\n  # NOTE: the featureGates value is deprecated and will be replaced by featureGatesMap in the future.\n  featureGates: \"\"\n  # The featureGatesMap will enable or disable specific feature gates in the operator as well as deploy any prerequisites for the feature gate.\n  # If this property is not an empty map, the featureGates property will be ignored.\n  featureGatesMap: {}\n  # operator.targetallocator.mtls: false\n  # operator.targetallocator.fallbackstrategy: false\n  # operator.collector.targetallocatorcr: false\n  # operator.sidecarcontainers.native: false\n  # operator.observability.prometheus: false\n  # operator.golang.flags: false\n  # operator.collector.default.config: false\n  ports:\n    metricsPort: 8080\n    webhookPort: 9443\n    healthzPort: 8081\n  resources: {}\n  # resources:\n  #   limits:\n  #     cpu: 100m\n  #     memory: 128Mi\n  #     ephemeral-storage: 50Mi\n  #   requests:\n  #     cpu: 100m\n  #     memory: 64Mi\n  #     ephemeral-storage: 50Mi\n\n  ## Adds additional environment variables. This property will be deprecated. Please use extraEnvs instead.\n  ## e.g ENV_VAR: env_value\n  env:\n    ENABLE_WEBHOOKS: \"true\"\n\n\n  # Extra definitions of environment variables.\n  extraEnvs: []\n  # - name: GOMEMLIMIT\n  #   valueFrom:\n  #     resourceFieldRef:\n  #       containerName: manager\n  #       resource: limits.memory\n\n  # -- Create the manager ServiceAccount\n  serviceAccount:\n    create: true\n    annotations: {}\n    ## Override the default name of the serviceaccount (the name of your installation)\n    name: \"\"\n\n  ## Enable ServiceMonitor for Prometheus metrics scrape\n  serviceMonitor:\n    enabled: false\n    # additional labels on the ServiceMonitor\n    extraLabels: {}\n    # add annotations on the ServiceMonitor\n    annotations: {}\n    metricsEndpoints:\n      - port: metrics\n    # Used to set relabeling and metricRelabeling configs on the ServiceMonitor\n    # https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config\n    relabelings: []\n    metricRelabelings: []\n\n  # Adds additional annotations to the manager Deployment\n  deploymentAnnotations: {}\n  # Adds additional annotations to the manager Service\n  serviceAnnotations: {}\n\n  podAnnotations: {}\n  podLabels: {}\n\n  prometheusRule:\n    enabled: false\n    groups: []\n    # Create default rules for monitoring the manager\n    defaultRules:\n      enabled: false\n      ## Additional labels for PrometheusRule alerts\n      additionalRuleLabels: {}\n      ## Additional annotations for PrometheusRule alerts\n      additionalRuleAnnotations: {}\n      ## Alerts are considered firing once they have been returned for this long.\n      duration: 5m\n    # additional labels on the PrometheusRule object\n    extraLabels: {}\n    # add annotations on the PrometheusRule object\n    annotations: {}\n    # change the default runbook urls.\n    # the alert name will get appended at the end of the url as an anchor.\n    runbookUrl: \"\"\n\n  # Whether the operator should create RBAC permissions for collectors. See README.md for more information.\n  createRbacPermissions: false\n  ## List of additional cli arguments to configure the manager\n  ## for example: --labels, etc.\n  extraArgs: []\n\n  ## Enable leader election mechanism for protecting against split brain if multiple operator pods/replicas are started.\n  ## See more at https://docs.openshift.com/container-platform/4.10/operators/operator_sdk/osdk-leader-election.html\n  leaderElection:\n    enabled: true\n\n  # Enable vertical pod autoscaler support for the manager\n  verticalPodAutoscaler:\n    enabled: false\n    # List of resources that the vertical pod autoscaler can control. Defaults to cpu, memory and ephemeral-storage.\n    controlledResources: []\n\n    # Define the max allowed resources for the pod\n    maxAllowed: {}\n    # cpu: 200m\n    # memory: 100Mi\n    # ephemeral-storage: 50Mi\n    # Define the min allowed resources for the pod\n    minAllowed: {}\n    # cpu: 200m\n    # memory: 100Mi\n    # ephemeral-storage: 50Mi\n\n    updatePolicy:\n      # Specifies whether recommended updates are applied when a Pod is started and whether recommended updates\n      # are applied during the life of a Pod. Possible values are \"Off\", \"Initial\", \"Recreate\", and \"Auto\".\n      updateMode: Auto\n      # Minimal number of replicas which need to be alive for Updater to attempt pod eviction.\n      # Only positive values are allowed. The default is 2.\n      minReplicas: 2\n  # Enable manager pod automatically rolling\n  rolling: false\n\n  ## Container specific securityContext\n  ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container\n  securityContext: {}\n  # allowPrivilegeEscalation: false\n  # capabilities:\n  #   drop:\n  #   - ALL\n\n## Provide OpenTelemetry Operator kube-rbac-proxy container image.\n##\nkubeRBACProxy:\n  enabled: true\n  image:\n    repository: quay.io/brancz/kube-rbac-proxy\n    tag: v0.18.1\n  ports:\n    proxyPort: 8443\n  resources: {}\n  # resources:\n  #   limits:\n  #     cpu: 500m\n  #     memory: 128Mi\n  #   requests:\n  #     cpu: 5m\n  #     memory: 64Mi\n\n  ## List of additional cli arguments to configure the kube-rbac-proxy\n  ## for example: --tls-cipher-suites, --tls-min-version, etc.\n  extraArgs: []\n\n  ## Container specific securityContext\n  ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container\n  securityContext: {}\n  # allowPrivilegeEscalation: false\n  # capabilities:\n  #   drop:\n  #   - ALL\n\n## Admission webhooks make sure only requests with correctly formatted rules will get into the Operator.\n## They also enable the sidecar injection for OpenTelemetryCollector and Instrumentation CR's\nadmissionWebhooks:\n  create: true\n  servicePort: 443\n  failurePolicy: Fail\n  secretName: \"\"\n\n  ## Defines the sidecar injection logic in Pods.\n  ## - Ignore, the injection is fail-open. The pod will be created, but the sidecar won't be injected.\n  ## - Fail, the injection is fail-close. If the webhook pod is not ready, pods cannot be created.\n  pods:\n    failurePolicy: Ignore\n\n  ## Adds a prefix to the mutating webhook name.\n  ## This can be used to order this mutating webhook with all your cluster's mutating webhooks.\n  namePrefix: \"\"\n\n  ## Customize webhook timeout duration\n  timeoutSeconds: 10\n\n  ## Provide selectors for your objects\n  namespaceSelector: {}\n  objectSelector: {}\n\n  ## https://github.com/open-telemetry/opentelemetry-helm-charts/blob/main/charts/opentelemetry-operator/README.md#tls-certificate-requirement\n  ## TLS Certificate Option 1: Use certManager to generate self-signed certificate.\n  ## certManager must be enabled. If enabled, always takes precedence over options 2 and 3.\n  certManager:\n    enabled: true\n    ## Provide the issuer kind and name to do the cert auth job.\n    ## By default, OpenTelemetry Operator will use self-signer issuer.\n    issuerRef: {}\n    # kind:\n    # name:\n    ## Annotations for the cert and issuer if cert-manager is enabled.\n    certificateAnnotations: {}\n    issuerAnnotations: {}\n    # duration must be specified by a Go time.Duration (ending in s, m or h)\n    duration: \"\"\n    # renewBefore must be specified by a Go time.Duration (ending in s, m or h)\n    # Take care when setting the renewBefore field to be very close to the duration\n    # as this can lead to a renewal loop, where the Certificate is always in the renewal period.\n    renewBefore: \"\"\n\n  ## TLS Certificate Option 2: Use Helm to automatically generate self-signed certificate.\n  ## certManager must be disabled and autoGenerateCert must be enabled.\n  ## If true and certManager.enabled is false, Helm will automatically create a self-signed cert and secret for you.\n  autoGenerateCert:\n    enabled: true\n    # If set to true, new webhook key/certificate is generated on helm upgrade.\n    recreate: true\n    # Cert period time in days. The default is 365 days.\n    certPeriodDays: 365\n\n  ## TLS Certificate Option 3: Use your own self-signed certificate.\n  ## certManager and autoGenerateCert must be disabled and certFile, keyFile, and caFile must be set.\n  ## The chart reads the contents of the file paths with the helm .Files.Get function.\n  ## Refer to this doc https://helm.sh/docs/chart_template_guide/accessing_files/ to understand\n  ## limitations of file paths accessible to the chart.\n  ## Path to your own PEM-encoded certificate.\n  certFile: \"\"\n  ## Path to your own PEM-encoded private key.\n  keyFile: \"\"\n  ## Path to the CA cert.\n  caFile: \"\"\n\n  # Adds additional annotations to the admissionWebhook Service\n  serviceAnnotations: {}\n\n  ## Secret annotations\n  secretAnnotations: {}\n  ## Secret labels\n  secretLabels: {}\n\n## Install CRDS with the right webhook settings\n## These are installed as templates, so they will clash with existing OpenTelemetry Operator CRDs in your cluster that are not already managed by the helm chart.\n## See https://github.com/open-telemetry/opentelemetry-helm-charts/blob/main/charts/opentelemetry-operator/UPGRADING.md#0560-to-0570 for more details.\ncrds:\n  create: true\n\n## Create the provided Roles and RoleBindings\n##\nrole:\n  create: true\n\n## Create the provided ClusterRoles and ClusterRoleBindings\n##\nclusterRole:\n  create: true\n\naffinity: {}\ntolerations: []\nnodeSelector: {}\ntopologySpreadConstraints: []\nhostNetwork: false\n\n# Allows for pod scheduler prioritisation\npriorityClassName: \"\"\n\n## SecurityContext holds pod-level security attributes and common container settings.\n## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/\nsecurityContext:\n  runAsGroup: 65532\n  runAsNonRoot: true\n  runAsUser: 65532\n  fsGroup: 65532\n\ntestFramework:\n  image:\n    repository: busybox\n    tag: latest"
  },
  {
    "path": "integration_test/testdata/otel/instrumentation.yml",
    "content": "apiVersion: opentelemetry.io/v1alpha1\nkind: Instrumentation\nmetadata:\n  name: otel-instrumentation\nspec:\n  exporter:\n    endpoint: http://alloy.__NAMESPACE__.svc.cluster.local:4318\n  propagators:\n    - tracecontext\n    - baggage\n  sampler:\n    type: parentbased_traceidratio  # see https://opentelemetry.io/docs/languages/erlang/sampling/#parentbasedsampler\n    argument: \".2\"\n  env:\n    - name: OTEL_EXPORTER_OTLP_INSECURE\n      value: \"true\"\n    - name: OTEL_LOG_LEVEL\n      value: \"debug\"\n    - name: OTEL_TRACES_EXPORTER\n      value: \"otlp\"\n    - name: OTEL_METRICS_EXPORTER\n      value: \"none\"\n    - name: OTEL_LOGS_EXPORTER\n      value: \"none\"\n#    - name: PYROSCOPE_SERVER_ADDRESS\n#      value: \"http://pyroscope.__NAMESPACE__.svc.cluster.local:4040\"\n    - name: PYROSCOPE_ENABLED\n      value: \"false\"\n    - name: OTEL_PROFILING_START_PROFILING\n      value: \"false\"\n  go:\n    image: ghcr.io/open-telemetry/opentelemetry-go-instrumentation/autoinstrumentation-go:v0.23.0\n    env:\n      - name: OTEL_EXPORTER_OTLP_ENDPOINT\n        value: http://alloy.__NAMESPACE__.svc.cluster.local:4318\n  java:\n    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:latest\n    env:\n      - name: OTEL_EXPORTER_OTLP_ENDPOINT\n        value: http://alloy.__NAMESPACE__.svc.cluster.local:4318"
  },
  {
    "path": "integration_test/testdata/routing-values.yaml",
    "content": "client:\n  routing:\n    # add the following subnets to the client's virtual network interface\n    # array of strings\n    alsoProxySubnets:  [\"11.11.11.11/32\", \"12.12.12.12/32\"]\n    neverProxySubnets: [\"13.11.11.11/32\", \"14.12.12.12/32\"]"
  },
  {
    "path": "integration_test/testdata/scripts/veth-down.sh",
    "content": "#!/usr/bin/bash\nip link set vm2 down\nip link set brm down\n\nfor var in \"$@\"\ndo\n  ip addr del \"$(echo \"$var\" | sed -r 's/\\.0$/.1/')\" dev brm\n  ip addr del \"$(echo \"$var\" | sed -r 's/\\.0$/.2/')\" dev vm2\ndone\n\nip link del brm type bridge\nip link set dev tapm down\nip tuntap del tapm mode tap\nip link set dev vm1 down\nip link del dev vm1 type veth peer name vm2\n"
  },
  {
    "path": "integration_test/testdata/scripts/veth-up.sh",
    "content": "#!/usr/bin/bash\nip link add dev vm1 type veth peer name vm2\nip link set dev vm1 up\nip tuntap add tapm mode tap\nip link set dev tapm up\nip link add brm type bridge\n\nip link set tapm master brm\nip link set vm1 master brm\n\nfor var in \"$@\"\ndo\n  ip addr add \"$(echo \"$var\" | sed -r 's/\\.0$/.1/')\" dev brm\n  ip addr add \"$(echo \"$var\" | sed -r 's/\\.0$/.2/')\" dev vm2\ndone\n\nip link set brm up\nip link set vm2 up"
  },
  {
    "path": "integration_test/testdata/stdiotest/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\ntype runner struct {\n\tuseStdErr bool\n\tnoNL      bool\n}\n\nfunc main() {\n\tr := runner{}\n\tcmd := cobra.Command{\n\t\tUse:   \"stdiotest\",\n\t\tShort: \"Test stdin, stdout, and stderr\",\n\t\tLong:  \"stdiotest will echo either echo its arguments, or when no arguments are given, its stdin\",\n\t\tRunE:  r.run,\n\t}\n\tflags := cmd.Flags()\n\tflags.BoolVarP(&r.useStdErr, \"stderr\", \"e\", false, \"send output to stderr (default is stdout)\")\n\tflags.BoolVarP(&r.useStdErr, \"nonl\", \"n\", false, \"don't append a newline to each echoed argument (invalid when echoing stdin)\")\n\tif err := cmd.Execute(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(1)\n\t}\n}\n\nfunc (r runner) run(cmd *cobra.Command, args []string) error {\n\tvar out io.Writer\n\tif r.useStdErr {\n\t\tout = cmd.ErrOrStderr()\n\t} else {\n\t\tout = cmd.OutOrStdout()\n\t}\n\tif len(args) == 0 {\n\t\t_, err := io.Copy(out, cmd.InOrStdin())\n\t\treturn err\n\t}\n\n\tif r.noNL {\n\t\tfor _, arg := range args {\n\t\t\tif _, err := out.Write([]byte(arg)); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor _, arg := range args {\n\t\t\tif _, err := fmt.Fprintln(out, arg); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "integration_test/testdata/udp-echo/Dockerfile",
    "content": "FROM golang:alpine3.15 AS builder\n\nWORKDIR /udp-echo\nCOPY go.mod .\nCOPY main.go .\nRUN go build -o udp-echo .\n\nFROM alpine:3.15\nCOPY --from=builder /udp-echo/udp-echo /usr/local/bin/\nENTRYPOINT [\"/usr/local/bin/udp-echo\"]\n"
  },
  {
    "path": "integration_test/testdata/udp-echo/go.mod",
    "content": "module local\n\ngo 1.19\n"
  },
  {
    "path": "integration_test/testdata/udp-echo/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n)\n\nfunc main() {\n\tportsEnv := os.Getenv(\"PORTS\")\n\tif portsEnv == \"\" {\n\t\tportsEnv = os.Getenv(\"PORT\")\n\t}\n\tif portsEnv == \"\" {\n\t\tportsEnv = \"8080\"\n\t}\n\tports := strings.Split(portsEnv, \",\")\n\twg := sync.WaitGroup{}\n\twg.Add(len(ports))\n\tfor _, port := range ports {\n\t\tport := port // pin it\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tfmt.Printf(\"UDP-echo server listening on port %s.\\n\", port)\n\t\t\tdefer fmt.Printf(\"UDP-echo server on port %s exited.\\n\", port)\n\t\t\tpc, err := net.ListenPacket(\"udp\", \":\"+port)\n\t\t\tif err == nil {\n\t\t\t\terr = serveConnection(pc)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\t\t}\n\t\t}()\n\t}\n\twg.Wait()\n}\n\nfunc serveConnection(pc net.PacketConn) error {\n\tbuf := [0x10000]byte{}\n\tpfx := []byte(\"Reply from UDP-echo: \")\n\tfor {\n\t\tn, addr, err := pc.ReadFrom(buf[:])\n\t\tif n > 0 {\n\t\t\tsb := string(buf[:n])\n\t\t\tif strings.HasSuffix(sb, \"\\n\") {\n\t\t\t\tfmt.Print(sb)\n\t\t\t} else {\n\t\t\t\tfmt.Println(sb)\n\t\t\t}\n\t\t\tif n == 5 && sb == \"exit\\n\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tr := make([]byte, len(pfx)+n)\n\t\t\tcopy(r, pfx)\n\t\t\tcopy(r[len(pfx):], buf[:n])\n\t\t\tif _, werr := pc.WriteTo(r, addr); werr != nil {\n\t\t\t\tfmt.Fprintln(os.Stderr, werr.Error())\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "integration_test/tls_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n)\n\nfunc (s *dockerDaemonSuite) Test_TLSAnnotations() {\n\tif !(s.ManagerIsVersion(\">2.24.x\") && s.ClientIsVersion(\">2.24.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility tests. Versions < 2.25.0 have no support for http intercepts\")\n\t}\n\tconst (\n\t\tsvc           = \"hello\"\n\t\tcontainerPort = 8443\n\t\timage         = \"echo-server:latest\"\n\t\tregistry      = \"ghcr.io/telepresenceio\"\n\t)\n\n\ttlsAppTemplate := itest.Generic{\n\t\tName: svc,\n\t\tServicePorts: []itest.ServicePort{\n\t\t\t{\n\t\t\t\tNumber:     443,\n\t\t\t\tName:       \"https\",\n\t\t\t\tTargetPort: \"https\",\n\t\t\t},\n\t\t},\n\t\tContainerPorts: []itest.ContainerPort{\n\t\t\t{\n\t\t\t\tNumber: containerPort,\n\t\t\t\tName:   \"https\",\n\t\t\t},\n\t\t},\n\t\tEnvironment: []core.EnvVar{\n\t\t\t{\n\t\t\t\tName:  \"PORTS\",\n\t\t\t\tValue: fmt.Sprintf(\"%d:https\", containerPort),\n\t\t\t},\n\t\t},\n\t\tImage:    image,\n\t\tRegistry: registry,\n\t\tVolumes: []core.Volume{\n\t\t\t{\n\t\t\t\tName: \"tls\",\n\t\t\t\tVolumeSource: core.VolumeSource{\n\t\t\t\t\tSecret: &core.SecretVolumeSource{SecretName: \"tel-cert\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tVolumeMounts: []core.VolumeMount{\n\t\t\t{\n\t\t\t\tName:      \"tls\",\n\t\t\t\tReadOnly:  true,\n\t\t\t\tMountPath: \"/certs\",\n\t\t\t},\n\t\t},\n\t}\n\n\tctx := docker.EnableClient(s.Context())\n\trq := s.Require()\n\tdockerCli, err := docker.GetClient(ctx)\n\trq.NoError(err)\n\tk8s := filepath.Join(itest.GetOSSRoot(ctx), \"testdata\", \"k8s\")\n\tgenericPath := filepath.Join(k8s, \"generic.goyaml\")\n\trq.NoError(itest.Kubectl(ctx, s.AppNamespace(), \"apply\", \"-f\", filepath.Join(k8s, \"tel-cert.yaml\")))\n\n\ts.TelepresenceConnect(ctx, \"--docker\")\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\ttype testData struct {\n\t\ttestName     string\n\t\tplainText    bool\n\t\theader       string\n\t\tanns         map[string]string\n\t\terrorPattern string\n\t}\n\n\ttts := []testData{\n\t\t{\n\t\t\t\"Client TLS and cert secret\",\n\t\t\tfalse,\n\t\t\t\"x:y\",\n\t\t\tmap[string]string{\n\t\t\t\tannotation.DownstreamTLSSecret + fmt.Sprintf(\".%d\", containerPort):        \"tel-cert\",\n\t\t\t\tannotation.UpstreamInsecureSkipVerify + fmt.Sprintf(\".%d\", containerPort): \"enabled\",\n\t\t\t\tannotation.InjectTrafficAgent:                                             \"enabled\",\n\t\t\t},\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"Client plaintext and cert secret\",\n\t\t\ttrue,\n\t\t\t\"x:y\",\n\t\t\tmap[string]string{\n\t\t\t\tannotation.DownstreamTLSSecret + fmt.Sprintf(\".%d\", containerPort):        \"tel-cert\",\n\t\t\t\tannotation.UpstreamInsecureSkipVerify + fmt.Sprintf(\".%d\", containerPort): \"enabled\",\n\t\t\t\tannotation.InjectTrafficAgent:                                             \"enabled\",\n\t\t\t},\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"Client TLS and cert path\",\n\t\t\tfalse,\n\t\t\t\"x:y\",\n\t\t\tmap[string]string{\n\t\t\t\tannotation.DownstreamCertificatePath + fmt.Sprintf(\".%d\", containerPort):  \"/certs\",\n\t\t\t\tannotation.UpstreamInsecureSkipVerify + fmt.Sprintf(\".%d\", containerPort): \"enabled\",\n\t\t\t\tannotation.InjectTrafficAgent:                                             \"enabled\",\n\t\t\t},\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"Client TLS and cert path, not insecure\",\n\t\t\tfalse,\n\t\t\t\"x:y\",\n\t\t\tmap[string]string{\n\t\t\t\tannotation.DownstreamCertificatePath + fmt.Sprintf(\".%d\", containerPort): \"/certs\",\n\t\t\t\tannotation.InjectTrafficAgent:                                            \"enabled\",\n\t\t\t},\n\t\t\t\"failed to verify certificate\",\n\t\t},\n\t\t{\n\t\t\t\"Client mTLS and cert path\",\n\t\t\tfalse,\n\t\t\t\"x:y\",\n\t\t\tmap[string]string{\n\t\t\t\tannotation.DownstreamCertificatePath + fmt.Sprintf(\".%d\", containerPort):  \"/certs\",\n\t\t\t\tannotation.UpstreamCertificatePath + fmt.Sprintf(\".%d\", containerPort):    \"/certs\",\n\t\t\t\tannotation.UpstreamInsecureSkipVerify + fmt.Sprintf(\".%d\", containerPort): \"enabled\",\n\t\t\t\tannotation.InjectTrafficAgent:                                             \"enabled\",\n\t\t\t},\n\t\t\t\"\",\n\t\t},\n\t}\n\n\tfor i, tt := range tts {\n\t\ts.Run(tt.testName, func() {\n\t\t\tctx = s.Context()\n\t\t\tttSvc := fmt.Sprintf(\"%s-%d\", svc, i)\n\t\t\ttpl := tlsAppTemplate\n\t\t\ttpl.Name = ttSvc\n\t\t\ttpl.Annotations = tt.anns\n\t\t\ts.ApplyTemplate(ctx, genericPath, &tpl)\n\t\t\tdefer s.DeleteTemplate(ctx, genericPath, &tpl)\n\n\t\t\trq := s.Require()\n\t\t\tctx, cancel := context.WithCancel(ctx)\n\t\t\twg := sync.WaitGroup{}\n\t\t\twg.Add(1)\n\t\t\tdefer wg.Wait()\n\t\t\tdefer cancel()\n\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tdefer cancel()\n\t\t\t\targs := make([]string, 0, 15)\n\t\t\t\targs = append(args, \"intercept\", ttSvc)\n\t\t\t\tif tt.plainText {\n\t\t\t\t\targs = append(args, \"--plaintext\", \"-p\", \"8080:https\")\n\t\t\t\t} else {\n\t\t\t\t\targs = append(args, \"-p\", \"8443:https\")\n\t\t\t\t}\n\t\t\t\tif tt.header != \"\" {\n\t\t\t\t\targs = append(args, \"--http-header\", tt.header)\n\t\t\t\t}\n\t\t\t\targs = append(args,\n\t\t\t\t\t\"--mount=false\",\n\t\t\t\t\t\"--docker-run\",\n\t\t\t\t\t\"--\", \"--name\", ttSvc+\".local\")\n\t\t\t\tif tt.plainText {\n\t\t\t\t\targs = append(args, \"-e\", \"PORTS=8080:http\")\n\t\t\t\t}\n\t\t\t\targs = append(args, registry+\"/\"+image)\n\t\t\t\tstdout, _, err := itest.Telepresence(ctx, args...)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"stdout: %s\", stdout)\n\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t} else {\n\t\t\t\t\tclog.Infof(ctx, \"stdout: %s\", stdout)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\trq.EventuallyContext(ctx, func() bool {\n\t\t\t\tir, err := dockerCli.ContainerInspect(ctx, ttSvc+\".local\")\n\t\t\t\treturn err == nil && ir.State.Running\n\t\t\t}, 30*time.Second, 3*time.Second, \"expected intercepted status never arrived\")\n\t\t\ts.CapturePodLogs(ctx, ttSvc, \"traffic-agent\", s.AppNamespace())\n\n\t\t\tsi := itest.TelepresenceStatusOk(ctx)\n\t\t\trq.True(len(si.UserDaemon.Intercepts) == 1)\n\t\t\trq.Equal(si.UserDaemon.Intercepts[0].Name, ttSvc)\n\n\t\t\trq.EventuallyContext(ctx, func() bool {\n\t\t\t\targs := []string{\"curl\", \"--max-time\", \"2\", \"-s\", \"-w\", \"\\nStatus: %{http_code}\\n\", \"-k\"}\n\t\t\t\tif tt.header != \"\" {\n\t\t\t\t\targs = append(args, \"-H\", tt.header)\n\t\t\t\t}\n\t\t\t\targs = append(args, fmt.Sprintf(\"https://%s\", ttSvc))\n\t\t\t\tso, se, err := itest.Telepresence(ctx, args...)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif se != \"\" {\n\t\t\t\t\t\tclog.Error(ctx, se)\n\t\t\t\t\t}\n\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tclog.Info(ctx, so)\n\t\t\t\tif tt.errorPattern != \"\" {\n\t\t\t\t\tok, err := regexp.MatchString(tt.errorPattern, so)\n\t\t\t\t\treturn err == nil && ok\n\t\t\t\t}\n\t\t\t\treturn strings.Contains(so, \"HTTP/2.0 GET /\")\n\t\t\t}, 10*time.Second, 3*time.Second, \"expected curl response never arrived\")\n\n\t\t\t// Terminate the ongoing intercept\n\t\t\tso, se, err := itest.Telepresence(ctx, \"leave\", ttSvc)\n\t\t\tif so != \"\" {\n\t\t\t\tclog.Info(ctx, so)\n\t\t\t}\n\t\t\tif se != \"\" {\n\t\t\t\tclog.Info(ctx, se)\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t\tcancel()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "integration_test/to_pod_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"regexp\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\nfunc (s *connectedSuite) Test_ToPodPortForwarding() {\n\tconst svc = \"echo-w-sidecars\"\n\tctx := s.Context()\n\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\trequire := s.Require()\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc, \"--port\", \"8080\", \"--to-pod\", \"8081\", \"--to-pod\", \"8082\")\n\tdefer itest.TelepresenceOk(ctx, \"leave\", svc)\n\trequire.Contains(stdout, \"Using Deployment \"+svc)\n\ts.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--intercepts\")\n\t\treturn err == nil && regexp.MustCompile(svc+`\\s*: intercepted`).MatchString(stdout)\n\t}, 10*time.Second, time.Second)\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts.Eventually(func() bool {\n\t\t\treturn itest.Run(ctx, \"curl\", \"--silent\", \"--max-time\", \"0.5\", \"localhost:8081\") == nil\n\t\t}, 30*time.Second, 2*time.Second, \"Forwarded port is not reachable as localhost:8081\")\n\t}()\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts.Eventually(func() bool {\n\t\t\treturn itest.Run(ctx, \"curl\", \"--silent\", \"--max-time\", \"0.5\", \"localhost:8082\") == nil\n\t\t}, 30*time.Second, 2*time.Second, \"Forwarded port is not reachable as localhost:8082\")\n\t}()\n\twg.Add(1)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\ts.Eventually(func() bool {\n\t\t\treturn itest.Run(ctx, \"curl\", \"--silent\", \"--max-time\", \"0.5\", \"localhost:8083\") != nil\n\t\t}, 30*time.Second, 2*time.Second, \"Non-forwarded port is reachable\")\n\t}()\n\twg.Wait()\n}\n\nfunc (s *connectedSuite) Test_ToPodUDPPortForwarding() {\n\tconst svc = \"echo-extra-udp\"\n\tctx := s.Context()\n\ts.ApplyApp(ctx, svc, \"deploy/\"+svc)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", svc)\n\n\trequire := s.Require()\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", svc, \"--port\", \"9080\", \"--to-pod\", \"8080/UDP\")\n\tdefer itest.TelepresenceOk(ctx, \"leave\", svc)\n\trequire.Contains(stdout, \"Using Deployment \"+svc)\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\trequire.Contains(stdout, svc+\": intercepted\")\n\titest.TelepresenceOk(ctx, \"loglevel\", \"trace\")\n\tdefer itest.TelepresenceOk(ctx, \"loglevel\", \"debug\")\n\ts.CapturePodLogs(ctx, svc, \"traffic-agent\", s.AppNamespace())\n\n\tconn, err := net.Dial(\"udp\", \"localhost:8080\")\n\trequire.NoError(err)\n\tdefer conn.Close()\n\n\tpingPong := func(msg string) {\n\t\t_ = conn.SetDeadline(time.Now().Add(10 * time.Second))\n\t\tbm := []byte(msg)\n\t\tn, err := conn.Write(bm)\n\t\trequire.NoError(err)\n\t\trequire.Equal(len(bm), n)\n\t\tbuf := [0x100]byte{}\n\t\tn, err = conn.Read(buf[:])\n\t\trequire.NoError(err)\n\t\trequire.Equal(fmt.Sprintf(\"Reply from UDP-echo: %s\", msg), string(buf[0:n]))\n\t}\n\n\tpingPong(\"12345678\")\n\tpingPong(\"a slightly longer message\")\n}\n"
  },
  {
    "path": "integration_test/udp_test.go",
    "content": "package integration_test\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n)\n\nfunc (s *connectedSuite) TestUDPEcho() {\n\tctx := s.Context()\n\trequire := s.Require()\n\tsvc := \"udp-echo\"\n\ttag := \"ghcr.io/telepresenceio/udp-echo:latest\"\n\n\trequire.NoError(s.Kubectl(ctx, \"create\", \"deploy\", svc, \"--image\", tag))\n\trequire.NoError(s.Kubectl(ctx, \"expose\", \"deploy\", svc, \"--port\", \"80\", \"--protocol\", \"UDP\", \"--target-port\", \"8080\"))\n\tdefer func() {\n\t\t_ = s.Kubectl(ctx, \"delete\", \"svc,deploy\", svc)\n\t}()\n\trequire.NoError(s.RolloutStatusWait(ctx, \"deploy/\"+svc))\n\ts.CapturePodLogs(ctx, svc, \"udp-echo\", s.AppNamespace())\n\n\tvar conn net.Conn\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tvar err error\n\t\t\tconn, err = net.Dial(\"udp\", fmt.Sprintf(\"%s.%s:80\", svc, s.AppNamespace()))\n\t\t\treturn err == nil\n\t\t},\n\t\t12*time.Second, // waitFor\n\t\t3*time.Second,  // polling interval\n\t\t`dial never succeeds`)\n\n\tdefer conn.Close()\n\n\tmb := strings.Builder{}\n\tmb.WriteString(\"This is \")\n\titm := \"a russian doll containing \"\n\tcount := 1000\n\tif runtime.GOOS == \"darwin\" {\n\t\t// Max UDP message size is 9216 bytes\n\t\tcount = 9000 / len(itm)\n\t}\n\tfor i := 0; i < count; i++ {\n\t\tmb.WriteString(itm)\n\t}\n\tmb.WriteString(\"a solid russian doll\")\n\n\tbuf := [0x10000]byte{}\n\n\techoTest := func(msg string) {\n\t\t_, err := conn.Write([]byte(msg))\n\t\trequire.NoError(err)\n\t\trequire.NoError(conn.SetReadDeadline(time.Now().Add(5 * time.Second)))\n\t\tn, err := conn.Read(buf[:])\n\t\trequire.Greater(n, 0)\n\t\tif errors.Is(err, io.EOF) {\n\t\t\terr = nil\n\t\t}\n\t\trequire.NoError(err)\n\t\trp := \"Reply from UDP-echo: \"\n\t\tpl := len(rp)\n\t\trequire.Equal(string(buf[:pl]), rp)\n\t\trequire.Equal(len(msg)+pl, n)\n\t\trequire.Equal(msg, string(buf[pl:n]))\n\t}\n\n\t// A UDP Dial will succeed immediately because it doesn't really connect, and even though the deployment is ready, the service\n\t// might not be listening just yet (there's no readiness probe). So we sleep a bit to give the service time to start.\n\ttime.Sleep(2 * time.Second)\n\techoTest(\"Hello\")\n\techoTest(mb.String())\n}\n"
  },
  {
    "path": "integration_test/uhn_dns_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n)\n\ntype unqualifiedHostNameDNSSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *unqualifiedHostNameDNSSuite) SuiteName() string {\n\treturn \"UnqualifiedHostNameDNS\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &unqualifiedHostNameDNSSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *unqualifiedHostNameDNSSuite) TearDownTest() {\n\titest.TelepresenceQuitOk(s.Context())\n}\n\nfunc (s *unqualifiedHostNameDNSSuite) Test_UHNExcludes() {\n\t// given\n\tctx := s.Context()\n\tserviceName := \"hey\"\n\tport, svcCancel := itest.StartLocalHttpEchoServer(ctx, serviceName)\n\tdefer svcCancel()\n\n\titest.ApplyEchoService(ctx, serviceName, s.AppNamespace(), port)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", serviceName)\n\n\texcludes := []string{\n\t\tserviceName,\n\t\tfmt.Sprintf(\"%s.%s\", serviceName, s.AppNamespace()),\n\t\tfmt.Sprintf(\"%s.%s.svc.cluster.local\", serviceName, s.AppNamespace()),\n\t}\n\tctx = itest.WithKubeConfigExtension(ctx, func(cluster *api.Cluster) map[string]any {\n\t\treturn map[string]any{\"dns\": map[string][]string{\n\t\t\t\"excludes\": excludes,\n\t\t}}\n\t})\n\n\t// when\n\ts.TelepresenceConnect(ctx, \"--context\", \"extra\")\n\n\t// then\n\tfor _, excluded := range excludes {\n\t\ts.Eventually(func() bool {\n\t\t\tconn, err := net.DialTimeout(\"tcp\", iputil.JoinHostPort(excluded, uint16(port)), 2*time.Second)\n\t\t\tif err == nil {\n\t\t\t\tclog.Errorf(ctx, \"excluded DNS name %s resolved to %s\", excluded, conn.RemoteAddr())\n\t\t\t\t_ = conn.Close()\n\t\t\t}\n\t\t\treturn err != nil\n\t\t}, 10*time.Second, 1*time.Second, \"should not be able to reach %s\", excluded)\n\t}\n\n\tstatus := itest.TelepresenceStatusOk(s.Context())\n\tassert.Equal(s.T(), excludes, status.RootDaemon.DNS.Excludes, \"Excludes in output\")\n}\n\nfunc (s *unqualifiedHostNameDNSSuite) Test_UHNMappings() {\n\t// given\n\tctx := s.Context()\n\tserviceName := \"echo\"\n\tport := 80\n\titest.ApplyEchoService(ctx, serviceName, s.AppNamespace(), port)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", serviceName)\n\n\tlocalPort, cancel := itest.StartLocalHttpEchoServer(ctx, \"my-hello\")\n\tdefer cancel()\n\n\taliasedService := fmt.Sprintf(\"%s.%s\", serviceName, s.AppNamespace())\n\tdnsMappings := client.DNSMappings{\n\t\t{\n\t\t\tName:     \"my-alias\",\n\t\t\tAliasFor: aliasedService,\n\t\t},\n\t\t{\n\t\t\tName:     fmt.Sprintf(\"my-alias.%s\", s.AppNamespace()),\n\t\t\tAliasFor: aliasedService,\n\t\t},\n\t\t{\n\t\t\tName:     \"my-alias.vx-root-domain.cluster.local\",\n\t\t\tAliasFor: aliasedService,\n\t\t},\n\t\t{\n\t\t\tName:     \"my-hello\",\n\t\t\tAliasFor: \"127.0.0.1\",\n\t\t},\n\t}\n\tmappings := make([]map[string]string, len(dnsMappings))\n\tfor i, dm := range dnsMappings {\n\t\tmappings[i] = map[string]string{\"name\": dm.Name, \"aliasFor\": dm.AliasFor}\n\t}\n\tctx = itest.WithKubeConfigExtension(ctx, func(cluster *api.Cluster) map[string]any {\n\t\treturn map[string]any{\"dns\": map[string]client.DNSMappings{\n\t\t\t\"mappings\": dnsMappings,\n\t\t}}\n\t})\n\n\t// when\n\ts.TelepresenceConnect(ctx, \"--context\", \"extra\")\n\n\t// then\n\tfor _, mapping := range dnsMappings {\n\t\tdialPort := port\n\t\tif mapping.Name == \"my-hello\" {\n\t\t\tdialPort = localPort\n\t\t}\n\t\ts.Eventually(func() bool {\n\t\t\tconn, err := net.DialTimeout(\"tcp\", iputil.JoinHostPort(mapping.Name, uint16(dialPort)), 5000*time.Millisecond)\n\t\t\tif err == nil {\n\t\t\t\t_ = conn.Close()\n\t\t\t}\n\t\t\treturn err == nil\n\t\t}, 10*time.Second, 1*time.Second, \"can find alias %s\", mapping.Name)\n\t}\n\n\tstatus := itest.TelepresenceStatusOk(s.Context())\n\tassert.Equal(s.T(), dnsMappings, status.RootDaemon.DNS.Mappings, \"Mappings in output\")\n}\n"
  },
  {
    "path": "integration_test/uninstall_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n)\n\nfunc (s *notConnectedSuite) Test_Uninstall() {\n\trequire := s.Require()\n\t// The telepresence-test-developer will not be able to uninstall everything\n\tctx := itest.WithUser(s.Context(), \"default\")\n\ts.TelepresenceConnect(ctx)\n\n\tnames := func() (string, error) {\n\t\treturn itest.KubectlOut(ctx, s.ManagerNamespace(),\n\t\t\t\"get\", \"svc,deploy\", agentconfig.ManagerAppName,\n\t\t\t\"--ignore-not-found\",\n\t\t\t\"-o\", \"jsonpath={.items[*].metadata.name}\")\n\t}\n\n\tstdout, err := names()\n\trequire.NoError(err)\n\trequire.Equal(2, len(strings.Split(stdout, \" \")), \"the string %q doesn't contain a service and a deployment\", stdout)\n\n\t// Add webhook agent to test webhook uninstall\n\tjobname := \"echo-auto-inject\"\n\tdeployname := \"deploy/\" + jobname\n\ts.ApplyApp(ctx, jobname, deployname)\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", jobname)\n\n\tverb := \"engage\"\n\tif !s.ClientIsVersion(\">2.21.x\") {\n\t\tverb = \"intercept\"\n\t}\n\ts.Eventually(func() bool {\n\t\tstdout, _, err = itest.Telepresence(ctx, \"list\", \"--agents\")\n\t\treturn err == nil && strings.Contains(stdout, fmt.Sprintf(\"%s: ready to %s (traffic-agent already installed)\", jobname, verb))\n\t}, 30*time.Second, 3*time.Second)\n\n\tstdout = itest.TelepresenceOk(ctx, \"helm\", \"uninstall\", \"-n\", s.ManagerNamespace())\n\tdefer s.TelepresenceHelmInstallOK(ctx, false)\n\ts.Contains(stdout, \"Traffic Manager uninstalled successfully\")\n\n\t// Double check webhook agent is uninstalled\n\trequire.NoError(s.RolloutStatusWait(ctx, deployname))\n\ts.Eventually(func() bool {\n\t\tstdout, err = s.KubectlOut(ctx, \"get\", \"pods\")\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tmatch, err := regexp.MatchString(jobname+`-[a-z0-9]+-[a-z0-9]+\\s+1/1\\s+Running`, stdout)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t\treturn false\n\t\t}\n\t\tif !match {\n\t\t\tclog.Infof(ctx, \"stdout = %s\", stdout)\n\t\t}\n\t\treturn err == nil && match\n\t}, itest.PodCreateTimeout(ctx), 2*time.Second)\n\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _ := names()\n\t\t\treturn stdout == \"\"\n\t\t},\n\t\t5*time.Second,        // waitFor\n\t\t500*time.Millisecond, // polling interval\n\t)\n}\n"
  },
  {
    "path": "integration_test/webhook_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\ntype webhookSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *webhookSuite) SuiteName() string {\n\treturn \"Webhook\"\n}\n\nfunc init() {\n\titest.AddConnectedSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &webhookSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *webhookSuite) Test_AutoInjectedAgent() {\n\tctx := s.Context()\n\ts.ApplyApp(ctx, \"echo-auto-inject\", \"deploy/echo-auto-inject\")\n\tdefer s.DeleteSvcAndWorkload(ctx, \"deploy\", \"echo-auto-inject\")\n\n\trequire := s.Require()\n\tverb := \"engage\"\n\tif !s.ClientIsVersion(\">2.21.x\") {\n\t\tverb = \"intercept\"\n\t}\n\trequire.Eventually(func() bool {\n\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--agents\")\n\t\treturn err == nil && strings.Contains(stdout, fmt.Sprintf(\"echo-auto-inject: ready to %s (traffic-agent already installed)\", verb))\n\t},\n\t\t20*time.Second, // waitFor\n\t\t2*time.Second,  // polling interval\n\t\t\"doesn't show up with agent installed in list output\",\n\t)\n\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", \"echo-auto-inject\", \"--port\", \"9091\")\n\tdefer itest.TelepresenceOk(ctx, \"leave\", \"echo-auto-inject\")\n\trequire.Contains(stdout, \"Using Deployment echo-auto-inject\")\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\trequire.Contains(stdout, \"echo-auto-inject: intercepted\")\n}\n\nfunc (s *notConnectedSuite) Test_AgentImageFromConfig() {\n\t// Use a config with agentImage to validate that it's the\n\t// latter that is used in the traffic-manager\n\tctx := itest.WithConfig(s.Context(), func(cfg client.Config) {\n\t\tcfg.Images().PrivateAgentImage = \"imageFromConfig:0.0.1\"\n\t})\n\n\ts.TelepresenceHelmInstallOK(itest.WithAgentImage(ctx, &itest.Image{}), true)\n\tdefer s.RollbackTM(ctx)\n\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceQuitOk(ctx)\n\n\tst := itest.TelepresenceStatusOk(ctx)\n\ts.Require().NotNil(st.TrafficManager)\n\ts.Equal(s.ManagerRegistry()+\"/imageFromConfig:0.0.1\", st.TrafficManager.TrafficAgent)\n}\n"
  },
  {
    "path": "integration_test/wiretap_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype wiretapSuite struct {\n\titest.Suite\n\titest.TrafficManager\n\tsvc      string\n\ttplPath  string\n\ttpl      *itest.Generic\n\thitCount int32\n}\n\nfunc (s *wiretapSuite) SuiteName() string {\n\treturn \"Wiretap\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &wiretapSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h, svc: \"echo-wt\"}\n\t})\n}\n\nfunc (s *wiretapSuite) SetupSuite() {\n\tif !(s.ManagerIsVersion(\">2.22.x\") && s.ClientIsVersion(\">2.22.x\")) {\n\t\ts.T().Skip(\"Not part of compatibility tests. The wiretap command was introduced in 2.23\")\n\t}\n\ts.Suite.SetupSuite()\n\ts.tplPath = filepath.Join(\"testdata\", \"k8s\", \"generic.goyaml\")\n\ts.tpl = &itest.Generic{\n\t\tName:       s.svc,\n\t\tTargetPort: \"http\",\n\t\tRegistry:   \"ghcr.io/telepresenceio\",\n\t\tImage:      \"echo-server:latest\",\n\t\tServicePorts: []itest.ServicePort{\n\t\t\t{\n\t\t\t\tNumber:     80,\n\t\t\t\tName:       \"http\",\n\t\t\t\tTargetPort: \"http-cp\",\n\t\t\t},\n\t\t},\n\t\tContainerPorts: []itest.ContainerPort{\n\t\t\t{\n\t\t\t\tNumber: 8080,\n\t\t\t\tName:   \"http-cp\",\n\t\t\t},\n\t\t},\n\t}\n\tctx := s.Context()\n\ts.ApplyTemplate(ctx, s.tplPath, s.tpl)\n\ts.NoError(s.RolloutStatusWait(ctx, \"deploy/\"+s.svc))\n}\n\nfunc (s *wiretapSuite) TearDownSuite() {\n\ts.DeleteTemplate(s.Context(), s.tplPath, s.tpl)\n}\n\nfunc (s *wiretapSuite) startWiretapHandler(ctx context.Context, name, addr string, echoTo *chan string) (int, context.CancelFunc) {\n\tctx, cancel := context.WithCancel(ctx)\n\tlc := net.ListenConfig{}\n\tl, err := lc.Listen(ctx, \"tcp\", addr)\n\trq := s.Require()\n\trq.NoError(err, \"failed to listen on localhost\")\n\tport := l.Addr().(*net.TCPAddr).Port\n\tsc := &http.Server{\n\t\tHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\tatomic.AddInt32(&s.hitCount, 1)\n\t\t\tif *echoTo != nil {\n\t\t\t\t*echoTo <- fmt.Sprintf(\"Request served by %s on port %d\\n\", name, port)\n\t\t\t}\n\t\t}),\n\t}\n\tgo func() {\n\t\t_ = sc.Serve(l)\n\t}()\n\tgo func() {\n\t\t<-ctx.Done()\n\t\tctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second)\n\t\tdefer cancel()\n\t\terr = sc.Shutdown(ctx)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"http server on %s exited with error: %v\", addr, err)\n\t\t} else {\n\t\t\tclog.Errorf(ctx, \"http server on %s exited\", addr)\n\t\t}\n\t}()\n\treturn port, cancel\n}\n\nfunc (s *wiretapSuite) Test_MultipleTapsOnOnePort() { //nolint:gocognit\n\tctx := s.Context()\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceQuitOk(ctx)\n\n\tvar out1 chan string\n\tlocalPort1, tap1HandlerCancel := s.startWiretapHandler(ctx, s.svc+\"-wt1\", \":0\", &out1)\n\tdefer tap1HandlerCancel()\n\n\tvar out2 chan string\n\tlocalPort2, tap2HandlerCancel := s.startWiretapHandler(ctx, s.svc+\"-wt2\", \":0\", &out2)\n\tdefer tap2HandlerCancel()\n\n\tlocalPort3, ihCancel := itest.StartLocalHttpEchoServer(ctx, s.svc)\n\tdefer ihCancel()\n\n\ts.Run(\"Place wiretap 1\", func() {\n\t\tso := itest.TelepresenceOk(ctx, \"wiretap\", \"--workload\", s.svc, \"--mount=false\", \"--port\", fmt.Sprintf(\"%d:80\", localPort1), \"wt1\")\n\t\ts.CapturePodLogs(ctx, s.svc, \"traffic-agent\", s.AppNamespace())\n\t\ts.Contains(so, \"Using Deployment \"+s.svc)\n\t})\n\n\tvar podName string\n\ts.Run(\"Find pod with agent\", func() {\n\t\t// Retrieve the name of the wiretapped pod\n\t\tctx := s.Context()\n\t\ts.Eventually(func() bool {\n\t\t\tpods := itest.RunningPodsWithAgents(ctx, s.svc, s.AppNamespace())\n\t\t\tclog.Infof(ctx, \"%s pods with agents: %v\", s.svc, pods)\n\t\t\tif len(pods) != 1 {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tpodName = pods[0]\n\t\t\treturn true\n\t\t}, 30*time.Second, 3*time.Second)\n\t})\n\n\ttype listOut struct {\n\t\tCmd    string                   `json:\"cmd\"`\n\t\tStdout []connector.WorkloadInfo `json:\"stdout\"`\n\t}\n\ts.Run(\"Place wiretap 2\", func() {\n\t\tso := itest.TelepresenceOk(s.Context(), \"wiretap\", \"--workload\", s.svc, \"--mount=false\", \"--port\", fmt.Sprintf(\"%d:80\", localPort2), \"wt2\")\n\t\ts.Contains(so, \"Using Deployment \"+s.svc)\n\t})\n\n\ts.Run(\"Verify taps\", func() {\n\t\tvar ok1, ok2 bool\n\t\tout1 = make(chan string, 5)\n\t\tdefer close(out1)\n\t\tout2 = make(chan string, 5)\n\t\tdefer close(out2)\n\t\ts.Eventually(func() bool {\n\t\t\tso, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"2\", s.svc)\n\t\t\t// Output must yield the standard response from the cluster's service\n\t\t\tif err != nil {\n\t\t\t\tclog.Errorf(ctx, \"curl: %s\", err)\n\t\t\t\treturn false\n\t\t\t}\n\n\t\t\tclog.Infof(ctx, \"curl output: %s\", so)\n\t\t\tif !strings.Contains(so, `Request served by `+podName) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tif !ok1 {\n\t\t\t\tselect {\n\t\t\t\tcase out := <-out1:\n\t\t\t\t\tok1 = strings.Contains(out, fmt.Sprintf(\"Request served by %s-wt1 on port %d\\n\", s.svc, localPort1))\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !ok2 {\n\t\t\t\tselect {\n\t\t\t\tcase out := <-out2:\n\t\t\t\t\tok2 = strings.Contains(out, fmt.Sprintf(\"Request served by %s-wt2 on port %d\\n\", s.svc, localPort2))\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ok1 && ok2\n\t\t}, 30*time.Second, 3*time.Second)\n\n\t\tso := itest.TelepresenceOk(ctx, \"list\", \"--wiretaps\", \"--output\", \"json\")\n\t\tvar soj listOut\n\t\ts.Require().NoError(json.Unmarshal([]byte(so), &soj))\n\t\tif s.Len(soj.Stdout, 1) {\n\t\t\tiis := soj.Stdout[0].InterceptInfo\n\t\t\tif s.Len(iis, 2) {\n\t\t\t\ts.True(iis[0].Spec.Wiretap)\n\t\t\t\ts.True(iis[1].Spec.Wiretap)\n\t\t\t}\n\t\t}\n\n\t\tso = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\", \"--output\", \"json\")\n\t\tsoj.Stdout = nil\n\t\ts.Require().NoError(json.Unmarshal([]byte(so), &soj))\n\t\ts.Len(soj.Stdout, 0)\n\t})\n\n\ts.Run(\"Verify tap concurrency\", func() {\n\t\t// Perform 100 http requests spread out over a one-second period.\n\t\tconst requestCount = int32(100)\n\t\tout1 = nil\n\t\tout2 = nil\n\t\tatomic.StoreInt32(&s.hitCount, 0)\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(int(requestCount))\n\t\tfor i := 0; i < int(requestCount); i++ {\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\ttime.Sleep(time.Duration(rand.Float64() * float64(time.Second)))\n\t\t\t\t_, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"30\", s.svc)\n\t\t\t\ts.NoError(err)\n\t\t\t}()\n\t\t}\n\t\twg.Wait()\n\t\ttime.Sleep(time.Second)\n\t\ts.Require().Equal(requestCount*2, atomic.LoadInt32(&s.hitCount))\n\t})\n\n\ts.Run(\"Intercept wiretapped service\", func() {\n\t\tout1 = make(chan string, 5)\n\t\tdefer close(out1)\n\t\tout2 = make(chan string, 5)\n\t\tdefer close(out2)\n\t\tso := itest.TelepresenceOk(ctx, \"intercept\", \"--mount=false\", \"--port\", fmt.Sprintf(\"%d:80\", localPort3), s.svc)\n\t\tdefer itest.TelepresenceOk(ctx, \"leave\", s.svc)\n\n\t\ts.Contains(so, \"Using Deployment \"+s.svc)\n\t\titest.PingInterceptedEchoServer(ctx, s.svc, \"80\")\n\n\t\t// Both handlers should have produced output.\n\t\tvar ok1, ok2 bool\n\t\ts.Eventually(func() bool {\n\t\t\tif !ok1 {\n\t\t\t\tselect {\n\t\t\t\tcase out := <-out1:\n\t\t\t\t\tok1 = strings.Contains(out, fmt.Sprintf(\"Request served by %s-wt1 on port %d\\n\", s.svc, localPort1))\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !ok2 {\n\t\t\t\tselect {\n\t\t\t\tcase out := <-out2:\n\t\t\t\t\tok2 = strings.Contains(out, fmt.Sprintf(\"Request served by %s-wt2 on port %d\\n\", s.svc, localPort2))\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ok1 && ok2\n\t\t}, 5*time.Second, 3*time.Second)\n\n\t\tso = itest.TelepresenceOk(ctx, \"list\", \"--wiretaps\", \"--output\", \"json\")\n\t\tvar soj listOut\n\t\ts.Require().NoError(json.Unmarshal([]byte(so), &soj))\n\t\tif s.Len(soj.Stdout, 1) {\n\t\t\tiis := soj.Stdout[0].InterceptInfo\n\t\t\tif s.Len(iis, 2) {\n\t\t\t\ts.True(iis[0].Spec.Wiretap)\n\t\t\t\ts.True(iis[1].Spec.Wiretap)\n\t\t\t}\n\t\t}\n\n\t\tso = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\", \"--output\", \"json\")\n\t\tsoj.Stdout = nil\n\t\ts.Require().NoError(json.Unmarshal([]byte(so), &soj))\n\t\tif s.Len(soj.Stdout, 1) {\n\t\t\tiis := soj.Stdout[0].InterceptInfo\n\t\t\tif s.Len(iis, 1) {\n\t\t\t\ts.False(iis[0].Spec.Wiretap)\n\t\t\t}\n\t\t}\n\t})\n\n\ts.Run(\"Verify taps after intercept\", func() {\n\t\tout1 = make(chan string, 5)\n\t\tdefer close(out1)\n\t\tout2 = make(chan string, 5)\n\t\tdefer close(out2)\n\n\t\tso, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"2\", s.svc)\n\t\ts.NoError(err)\n\t\t// Output must yield the standard response from the cluster's service\n\t\ts.Contains(so, `Request served by `+podName)\n\n\t\tvar ok1, ok2 bool\n\t\ts.Eventually(func() bool {\n\t\t\tif !ok1 {\n\t\t\t\tselect {\n\t\t\t\tcase out := <-out1:\n\t\t\t\t\tok1 = strings.Contains(out, fmt.Sprintf(\"Request served by %s-wt1 on port %d\\n\", s.svc, localPort1))\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !ok2 {\n\t\t\t\tselect {\n\t\t\t\tcase out := <-out2:\n\t\t\t\t\tok2 = strings.Contains(out, fmt.Sprintf(\"Request served by %s-wt2 on port %d\\n\", s.svc, localPort2))\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn ok1 && ok2\n\t\t}, 5*time.Second, 3*time.Second)\n\t})\n\n\ts.Run(\"Wiretaps gone\", func() {\n\t\tout1 = make(chan string, 5)\n\t\tdefer close(out1)\n\t\tout2 = make(chan string, 5)\n\t\tdefer close(out2)\n\n\t\titest.TelepresenceOk(ctx, \"leave\", \"wt1\")\n\t\titest.TelepresenceOk(ctx, \"leave\", \"wt2\")\n\n\t\tso, err := itest.Output(ctx, \"curl\", \"--silent\", \"--max-time\", \"2\", s.svc)\n\t\ts.NoError(err)\n\t\t// Out must yield the standard response from the cluster's service\n\t\ts.Contains(so, `Request served by `+podName)\n\n\t\t// Handlers should not have produced output.\n\t\tselect {\n\t\tcase out := <-out1:\n\t\t\ts.Failf(\"unexpected output from wiretap handler: %q\", out)\n\t\tcase out := <-out2:\n\t\t\ts.Failf(\"unexpected output from wiretap handler: %q\", out)\n\t\tcase <-time.After(2 * time.Second):\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "integration_test/workload_configuration_test.go",
    "content": "package integration_test\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n)\n\ntype workloadConfigurationSuite struct {\n\titest.Suite\n\titest.TrafficManager\n}\n\nfunc (s *workloadConfigurationSuite) SuiteName() string {\n\treturn \"WorkloadConfiguration\"\n}\n\nfunc init() {\n\titest.AddTrafficManagerSuite(\"-workload-configuration\", func(h itest.TrafficManager) itest.TestingSuite {\n\t\treturn &workloadConfigurationSuite{Suite: itest.Suite{Harness: h}, TrafficManager: h}\n\t})\n}\n\nfunc (s *workloadConfigurationSuite) disabledWorkloadKind(tp, wl string) {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\ts.ApplyApp(ctx, wl, strings.ToLower(tp)+\"/\"+wl)\n\tdefer s.DeleteSvcAndWorkload(ctx, strings.ToLower(tp), wl)\n\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\t// give it time for the workload to be detected (if it was going to be)\n\ttime.Sleep(6 * time.Second)\n\n\tlist := itest.TelepresenceOk(ctx, \"list\")\n\trequire.Equal(\"No Workloads (Deployments, StatefulSets, ReplicaSets, or Rollouts)\", list)\n\n\t_, stderr, err := itest.Telepresence(ctx, \"intercept\", wl)\n\trequire.Error(err)\n\trequire.Contains(stderr, fmt.Sprintf(\"connector.CreateIntercept: workload \\\"%s.%s\\\" not found\", wl, s.AppNamespace()))\n}\n\nfunc (s *workloadConfigurationSuite) Test_DisabledReplicaSet() {\n\ts.TelepresenceHelmInstallOK(s.Context(), true, \"--set\", \"workloads.replicaSets.enabled=false\")\n\tdefer s.TelepresenceHelmInstallOK(s.Context(), true, \"--set\", \"workloads.replicaSets.enabled=true\")\n\ts.disabledWorkloadKind(\"ReplicaSet\", \"rs-echo\")\n}\n\nfunc (s *workloadConfigurationSuite) Test_DisabledStatefulSet() {\n\ts.TelepresenceHelmInstallOK(s.Context(), true, \"--set\", \"workloads.statefulSets.enabled=false\")\n\tdefer s.TelepresenceHelmInstallOK(s.Context(), true, \"--set\", \"workloads.statefulSets.enabled=true\")\n\ts.disabledWorkloadKind(\"StatefulSet\", \"ss-echo\")\n}\n\nfunc (s *workloadConfigurationSuite) Test_InterceptsDeploymentWithDisabledReplicaSets() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\twl, tp := \"echo-one\", \"Deployment\"\n\ts.ApplyApp(ctx, wl, strings.ToLower(tp)+\"/\"+wl)\n\tdefer s.DeleteSvcAndWorkload(ctx, strings.ToLower(tp), wl)\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"workloads.replicaSets.enabled=false\")\n\tdefer s.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"workloads.replicaSets.enabled=true\")\n\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\tverb := \"engage\"\n\tif !s.ClientIsVersion(\">2.21.x\") {\n\t\tverb = \"intercept\"\n\t}\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\")\n\t\t\treturn err == nil && strings.Contains(stdout, fmt.Sprintf(\"%s: ready to %s\", wl, verb))\n\t\t},\n\t\t6*time.Second, // waitFor\n\t\t2*time.Second, // polling interval\n\t)\n\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", wl)\n\trequire.Contains(stdout, fmt.Sprintf(\"Using %s %s\", tp, wl))\n\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\trequire.Contains(stdout, fmt.Sprintf(\"%s: intercepted\", wl))\n\titest.TelepresenceOk(ctx, \"leave\", wl)\n}\n\nfunc (s *workloadConfigurationSuite) Test_InterceptsReplicaSetWithDisabledDeployments() {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\twl, tp := \"echo-easy\", \"Deployment\"\n\ts.ApplyApp(ctx, wl, strings.ToLower(tp)+\"/\"+wl)\n\tdefer s.DeleteSvcAndWorkload(ctx, strings.ToLower(tp), wl)\n\n\tinterceptableWl := s.KubectlOk(ctx, \"get\", \"replicasets\", \"-l\", fmt.Sprintf(\"app=%s\", wl), \"-o\", \"jsonpath={.items[*].metadata.name}\")\n\n\ts.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"logLevel=trace\", \"--set\", \"workloads.deployments.enabled=false\")\n\tdefer s.TelepresenceHelmInstallOK(ctx, true, \"--set\", \"workloads.deployments.enabled=true\")\n\n\ts.TelepresenceConnect(ctx)\n\tdefer itest.TelepresenceDisconnectOk(ctx)\n\n\tverb := \"engage\"\n\tif !s.ClientIsVersion(\">2.21.x\") {\n\t\tverb = \"intercept\"\n\t}\n\texpect := fmt.Sprintf(\"%s: ready to %s\", interceptableWl, verb)\n\trequire.Eventuallyf(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\")\n\t\t\tclog.Info(ctx, stdout)\n\t\t\treturn err == nil && strings.Contains(stdout, expect)\n\t\t},\n\t\t6*time.Second, // waitFor\n\t\t2*time.Second, // polling interval\n\t\t\"expected %q was never produced\", expect,\n\t)\n\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", interceptableWl)\n\trequire.Contains(stdout, fmt.Sprintf(\"Using %s %s\", \"ReplicaSet\", interceptableWl))\n\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\trequire.Contains(stdout, fmt.Sprintf(\"%s: intercepted\", interceptableWl))\n\titest.TelepresenceOk(ctx, \"leave\", interceptableWl)\n}\n"
  },
  {
    "path": "integration_test/workload_watch_test.go",
    "content": "package integration_test\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nfunc (s *notConnectedSuite) createIntercept(ctx context.Context, client manager.ManagerClient, session *manager.SessionInfo) (*manager.InterceptInfo, error) {\n\tir := &manager.CreateInterceptRequest{\n\t\tSession: session,\n\t\tInterceptSpec: &manager.InterceptSpec{\n\t\t\tName:         \"echo-easy\",\n\t\t\tClient:       \"telepresence@datawire.io\",\n\t\t\tAgent:        \"echo-easy\",\n\t\t\tWorkloadKind: \"Deployment\",\n\t\t\tNamespace:    s.AppNamespace(),\n\t\t\tMechanism:    \"tcp\",\n\t\t\tTargetHost:   \"127.0.0.1\",\n\t\t\tTargetPort:   8080,\n\t\t},\n\t}\n\tpi, err := client.PrepareIntercept(ctx, ir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tspec := ir.InterceptSpec\n\tspec.ServicePort = pi.ServicePort\n\tspec.ServicePortName = pi.ServicePortName\n\tspec.ServiceUid = pi.ServiceUid\n\tspec.ContainerPort = pi.ContainerPort\n\tspec.Protocol = pi.Protocol\n\tspec.ContainerName = pi.ContainerName\n\tif pi.ServiceUid != \"\" {\n\t\tif pi.ServicePortName != \"\" {\n\t\t\tspec.PortIdentifier = pi.ServicePortName\n\t\t} else {\n\t\t\tspec.PortIdentifier = strconv.Itoa(int(pi.ServicePort))\n\t\t}\n\t} else {\n\t\tspec.PortIdentifier = strconv.Itoa(int(pi.ContainerPort))\n\t}\n\treturn client.CreateIntercept(ctx, ir)\n}\n\nfunc (s *notConnectedSuite) Test_WorkloadListener() {\n\tif !s.ClientVersion().EQ(version.Structured) {\n\t\ts.T().Skip(`Not part of compatibility tests. DoWithTrafficManager assumes compiled executable`)\n\t}\n\ts.Require().NoError(s.DoWithTrafficManager(s.Context(), func(ctx context.Context, cancel context.CancelFunc, client manager.ManagerClient, session *manager.SessionInfo) {\n\t\trq := s.Require()\n\n\t\t// Perform some actions that will generate events. Here:\n\t\t// 1. Create a deployment\n\t\t// 2. Prepare an intercept on that deployment (injects the traffic-agent into the pod)\n\t\t// 3. Create an intercept (changes state to INTERCEPTED)\n\t\t// 4. Leave the intercept (state goes back to INSTALLED)\n\t\t// 5. Remove the deployment\n\t\tdefer cancel()\n\t\t_, err := client.SetLogLevel(ctx, &manager.LogLevelRequest{LogLevel: \"trace\"})\n\t\tif !s.NoError(err) {\n\t\t\treturn\n\t\t}\n\n\t\ttoCtx, toCancel := context.WithTimeout(ctx, time.Minute)\n\t\tdefer toCancel()\n\t\twwStream, err := client.WatchWorkloads(toCtx, &manager.WorkloadEventsRequest{\n\t\t\tSessionInfo: session,\n\t\t})\n\t\trq.NoError(err)\n\n\t\tdefer func() {\n\t\t\t_, _ = client.SetLogLevel(ctx, &manager.LogLevelRequest{LogLevel: \"debug\"})\n\t\t}()\n\n\t\ts.ApplyApp(ctx, \"echo-easy\", \"deploy/echo-easy\")\n\n\t\t// This map contains a key for each expected event from the workload watcher\n\t\texpectations := map[string]bool{\n\t\t\t\"added\":                 false,\n\t\t\t\"progressing\":           false,\n\t\t\t\"available\":             false,\n\t\t\t\"agent installed\":       false,\n\t\t\t\"agent intercepted\":     false,\n\t\t\t\"agent installed again\": false,\n\t\t\t\"deleted\":               false,\n\t\t}\n\n\t\tvar spec *manager.InterceptSpec\n\t\tvar interceptingClient string\n\t\tfor !(s.T().Failed() || expectations[\"deleted\"]) {\n\t\t\tdelta, err := wwStream.Recv()\n\t\t\tif err != nil {\n\t\t\t\tclog.Infof(ctx, \"watcher ended with %v\", err)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tfor _, ev := range delta.Events {\n\t\t\t\tclog.Infof(ctx, \"watcher event: %s %v\", ev.Type, ev.Workload)\n\t\t\t\tswitch ev.Type {\n\t\t\t\tcase manager.WorkloadEvent_ADDED_UNSPECIFIED, manager.WorkloadEvent_MODIFIED:\n\t\t\t\t\texpectations[\"added\"] = true\n\t\t\t\t\tswitch ev.Workload.State {\n\t\t\t\t\tcase manager.WorkloadInfo_PROGRESSING:\n\t\t\t\t\t\texpectations[\"progressing\"] = true\n\t\t\t\t\tcase manager.WorkloadInfo_AVAILABLE:\n\t\t\t\t\t\tif !expectations[\"available\"] {\n\t\t\t\t\t\t\texpectations[\"available\"] = true\n\t\t\t\t\t\t\tii, err := s.createIntercept(ctx, client, session)\n\t\t\t\t\t\t\tif !s.NoError(err) {\n\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tspec = ii.Spec\n\t\t\t\t\t\t}\n\t\t\t\t\t\tswitch ev.Workload.AgentState {\n\t\t\t\t\t\tcase manager.WorkloadInfo_INSTALLED:\n\t\t\t\t\t\t\tif expectations[\"agent intercepted\"] {\n\t\t\t\t\t\t\t\texpectations[\"agent installed again\"] = true\n\t\t\t\t\t\t\t\ts.DeleteSvcAndWorkload(ctx, \"deploy\", \"echo-easy\")\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\texpectations[\"agent installed\"] = true\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\tcase manager.WorkloadInfo_INTERCEPTED:\n\t\t\t\t\t\t\texpectations[\"agent installed\"] = true\n\t\t\t\t\t\t\texpectations[\"agent intercepted\"] = true\n\t\t\t\t\t\t\tif ics := ev.Workload.InterceptClients; len(ics) == 1 {\n\t\t\t\t\t\t\t\tinterceptingClient = ics[0].Client\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t_, err = client.RemoveIntercept(ctx, &manager.RemoveInterceptRequest2{\n\t\t\t\t\t\t\t\tSession: session,\n\t\t\t\t\t\t\t\tName:    spec.Name,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\ts.NoError(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase manager.WorkloadEvent_DELETED:\n\t\t\t\t\texpectations[\"deleted\"] = true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor k, expect := range expectations {\n\t\t\ts.True(expect, k)\n\t\t}\n\t\ts.Equal(\"telepresence@datawire.io\", interceptingClient)\n\t}))\n}\n"
  },
  {
    "path": "integration_test/workloads_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bytes\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/integration_test/itest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\nfunc (s *connectedSuite) uninstall(wl string) {\n\tctx := s.Context()\n\n\titest.TelepresenceOk(ctx, \"uninstall\", wl)\n\ts.Require().Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\", \"--agents\")\n\t\t\treturn err == nil && !strings.Contains(stdout, wl)\n\t\t},\n\t\t180*time.Second, // waitFor\n\t\t6*time.Second,   // polling interval\n\t)\n}\n\nfunc (s *connectedSuite) successfulIntercept(tp, wl, port string) {\n\tctx := s.Context()\n\ts.ApplyApp(ctx, wl, strings.ToLower(tp)+\"/\"+wl)\n\tdefer s.DeleteSvcAndWorkload(ctx, tp, wl)\n\n\ts.doIntercept(tp, wl, port)\n\tif !s.ClientIsVersion(\">2.21.x\") && s.ManagerIsVersion(\">2.21.x\") {\n\t\t// An <2.22.0 client will not be able to uninstall an agent when the traffic-manager is >=2.22.0\n\t\t// because the client will attempt to remove the entry in the telepresence-agents configmap. It\n\t\t// is no longer present in versions >=2.22.0\n\t\treturn\n\t}\n\ts.uninstall(wl)\n\ttpl := itest.DisruptionBudget{\n\t\tName:         \"telepresence-test\",\n\t\tMinAvailable: 1,\n\t}\n\n\t// Do the intercept again, this time with a disruption budget with minAvailable=1 in place.\n\trq := s.Require()\n\tdb, err := itest.ReadTemplate(ctx, filepath.Join(\"testdata\", \"k8s\", \"disruption-budget.goyaml\"), &tpl)\n\trq.NoError(err)\n\trq.NoError(s.Kubectl(dos.WithStdin(ctx, bytes.NewReader(db)), \"apply\", \"-f\", \"-\"))\n\tdefer func() {\n\t\t_ = s.Kubectl(dos.WithStdin(ctx, bytes.NewReader(db)), \"delete\", \"-f\", \"-\")\n\t}()\n\ts.doIntercept(tp, wl, port)\n}\n\nfunc (s *connectedSuite) doIntercept(tp, wl, port string) {\n\tctx := s.Context()\n\trequire := s.Require()\n\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\")\n\t\t\treturn err == nil && strings.Contains(stdout, wl)\n\t\t},\n\t\t6*time.Second, // waitFor\n\t\t2*time.Second, // polling interval\n\t)\n\n\tout, err := s.KubectlOut(ctx, \"get\", strings.ToLower(tp), wl, \"-o\", \"jsonpath={.spec.replicas}\")\n\trequire.NoError(err)\n\treplicas, err := strconv.Atoi(out)\n\trequire.NoError(err)\n\n\tstdout := itest.TelepresenceOk(ctx, \"intercept\", \"--mount\", \"false\", \"--port\", port, wl)\n\trequire.Contains(stdout, \"Using \"+tp+\" \"+wl)\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\trequire.Contains(stdout, wl+\": intercepted\")\n\trequire.NotContains(stdout, \"Volume Mount Point\")\n\ts.Eventually(func() bool {\n\t\tras := itest.RunningPodsWithAgents(ctx, wl, s.AppNamespace())\n\t\tclog.Infof(ctx, \"pod with agent count %d, expected %d\", len(ras), replicas)\n\t\treturn len(ras) == replicas\n\t}, 60*time.Second, 5*time.Second)\n\ts.CapturePodLogs(ctx, wl, \"traffic-agent\", s.AppNamespace())\n\ttime.Sleep(10 * time.Second)\n\titest.TelepresenceOk(ctx, \"leave\", wl)\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--intercepts\")\n\trequire.NotContains(stdout, wl+\": intercepted\")\n}\n\nfunc (s *connectedSuite) successfulIngest(tp, wl string) {\n\tctx := s.Context()\n\ts.ApplyApp(ctx, wl, strings.ToLower(tp)+\"/\"+wl)\n\tdefer s.DeleteSvcAndWorkload(ctx, tp, wl)\n\n\trequire := s.Require()\n\n\trequire.Eventually(\n\t\tfunc() bool {\n\t\t\tstdout, _, err := itest.Telepresence(ctx, \"list\")\n\t\t\treturn err == nil && strings.Contains(stdout, wl)\n\t\t},\n\t\t6*time.Second, // waitFor\n\t\t2*time.Second, // polling interval\n\t)\n\n\tstdout := itest.TelepresenceOk(ctx, \"ingest\", \"--mount\", \"false\", wl)\n\trequire.Contains(stdout, \"Using \"+tp+\" \"+wl)\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--ingests\")\n\trequire.Contains(stdout, wl+\": ingested\")\n\trequire.NotContains(stdout, \"Volume Mount Point\")\n\ts.CapturePodLogs(ctx, wl, \"traffic-agent\", s.AppNamespace())\n\titest.TelepresenceOk(ctx, \"leave\", wl)\n\tstdout = itest.TelepresenceOk(ctx, \"list\", \"--ingests\")\n\trequire.NotContains(stdout, wl+\": ingested\")\n\tif !s.ClientIsVersion(\">2.21.x\") && s.ManagerIsVersion(\">2.21.x\") {\n\t\t// An <2.22.0 client will not be able to uninstall an agent when the traffic-manager is >=2.22.0\n\t\t// because the client will attempt to remove the entry in the telepresence-agents configmap. It\n\t\t// is no longer present in versions >=2.22.0\n\t\treturn\n\t}\n\ts.uninstall(wl)\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyInterceptsDeploymentWithProbes() {\n\ts.successfulIntercept(\"Deployment\", \"with-probes\", \"9090\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyInterceptsReplicaSet() {\n\ts.successfulIntercept(\"ReplicaSet\", \"rs-echo\", \"9091\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyInterceptsStatefulSet() {\n\tif !s.ManagerIsVersion(\">2.21.x\") {\n\t\ts.T().Skip(\"Not part of compatibility tests. StatefulSet rollouts fail intermittently in versions < 2.22.0\")\n\t}\n\ts.successfulIntercept(\"StatefulSet\", \"ss-echo\", \"9092\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyInterceptsDeploymentWithNoVolumes() {\n\ts.successfulIntercept(\"Deployment\", \"echo-no-vols\", \"9093\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyInterceptsDeploymentWithoutService() {\n\ts.successfulIntercept(\"Deployment\", \"echo-no-svc-ann\", \"9094\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyIngestsDeploymentWithProbes() {\n\ts.successfulIngest(\"Deployment\", \"with-probes\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyIngestsReplicaSet() {\n\ts.successfulIngest(\"ReplicaSet\", \"rs-echo\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyIngestsStatefulSet() {\n\tif !s.ManagerIsVersion(\">2.21.x\") {\n\t\ts.T().Skip(\"Not part of compatibility tests. StatefulSet rollouts fail intermittently in versions < 2.22.0\")\n\t}\n\ts.successfulIngest(\"StatefulSet\", \"ss-echo\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyIngestsDeploymentWithNoVolumes() {\n\ts.successfulIngest(\"Deployment\", \"echo-no-vols\")\n}\n\nfunc (s *connectedSuite) Test_SuccessfullyIngestsDeploymentWithoutService() {\n\ts.successfulIngest(\"Deployment\", \"echo-no-svc\")\n}\n"
  },
  {
    "path": "integration_test/wpad_test.go",
    "content": "package integration_test\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\n// Test_WpadNotForwarded tests that DNS request aren't forwarded\n// to the cluster.\nfunc (s *connectedSuite) Test_WpadNotForwarded() {\n\tctx := s.Context()\n\tlogFile := filepath.Join(filelocation.AppUserLogDir(ctx), \"daemon.log\")\n\n\ttests := []struct {\n\t\tqn      string\n\t\tforward bool\n\t}{\n\t\t{\n\t\t\t\"wpad\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\tfmt.Sprintf(\"wpad.%s\", s.AppNamespace()),\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"wpad.cluster.local\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"wpad.svc.cluster.local\",\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\tfmt.Sprintf(\"wpad.%s.svc.cluster.local\", s.AppNamespace()),\n\t\t\tfalse,\n\t\t},\n\t\t/* revisit after checking relevant log messages on all platforms\n\t\t{\n\t\t\t\"wpad.bogus.nu\",\n\t\t\ttrue,\n\t\t},\n\t\t*/\n\t}\n\n\t// Figure out where the current end of the logfile is. This must be done before any\n\t// of the tests run because the queries that the DNS resolver receives are dependent\n\t// on how the system's DNS resolver handle search paths and caching.\n\tst, err := os.Stat(logFile)\n\ts.Require().NoError(err)\n\tpos := st.Size()\n\n\tfor _, tt := range tests {\n\t\ts.Run(tt.qn, func() {\n\t\t\trequire := s.Require()\n\t\t\tctx := s.Context()\n\n\t\t\t// Make an attempt to resolve the host\n\t\t\tshort, cancel := context.WithTimeout(ctx, 20*time.Millisecond)\n\t\t\tdefer cancel()\n\t\t\t_, _ = net.DefaultResolver.LookupIPAddr(short, tt.qn)\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\n\t\t\t// Seek to the end of the log as it were before the lookup\n\t\t\trootLog, err := os.Open(logFile)\n\t\t\trequire.NoError(err)\n\t\t\tdefer rootLog.Close()\n\t\t\t_, err = rootLog.Seek(pos, 0)\n\t\t\trequire.NoError(err)\n\n\t\t\t// Ensure that there's an A record with an NXDOMAIN but no LookupHost call\n\t\t\t// with a \"wpad.\" prefix. The host may not match exactly due to how the\n\t\t\t// OS handles search paths.\n\t\t\thasNX := false\n\t\t\thasLookup := false\n\t\t\tscn := bufio.NewScanner(rootLog)\n\t\t\tfor scn.Scan() {\n\t\t\t\ttxt := scn.Text()\n\t\t\t\tif strings.Contains(txt, \"wpad\") {\n\t\t\t\t\tif !hasLookup {\n\t\t\t\t\t\tif s.IsIPv6() {\n\t\t\t\t\t\t\thasLookup = strings.Contains(txt, \"Lookup AAAA \")\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thasLookup = strings.Contains(txt, \"Lookup A \")\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif !hasNX {\n\t\t\t\t\t\thasNX = strings.Contains(txt, \"-> NXDOMAIN\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif tt.qn == \"wpad\" && !hasNX && !hasLookup {\n\t\t\t\t// this is very likely OK because our DNS server never received the request. It\n\t\t\t\t// was filtered by the OS DNS framework. Those tests are only relevant when the overriding\n\t\t\t\t// DNS resolver is used.\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif tt.forward {\n\t\t\t\trequire.Truef(hasLookup, \"Missing expected Lookup A log for %s\", tt.qn)\n\t\t\t} else {\n\t\t\t\trequire.Falsef(hasLookup, \"Found unexpected Lookup A log for %s\", tt.qn)\n\t\t\t\trequire.Truef(hasNX, \"No NXDOMAIN record found for %s\", tt.qn)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "k8s/agent-injector-rbac.yaml",
    "content": "---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: traffic-manager\n  namespace: ambassador\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: traffic-manager\nrules:\n- apiGroups:\n  - \"\"\n  resources: [\"services\"]\n  verbs: [\"get\", \"list\"]\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: traffic-manager\nsubjects:\n- kind: ServiceAccount\n  name: traffic-manager\n  namespace: ambassador\nroleRef:\n  kind: ClusterRole\n  name: traffic-manager\n  apiGroup: rbac.authorization.k8s.io\n"
  },
  {
    "path": "k8s/agent-injector-secret.yaml",
    "content": "---\nkind: Secret\napiVersion: v1\nmetadata:\n  name: agent-injector-tls\n  namespace: ambassador\ntype: Opaque\ndata:\n  ## TLS certificate and key for `ambassador-injector.ambassador.svc`\n  crt.pem: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZnekNDQTJ1Z0F3SUJBZ0lFRHY3S3NUQU5CZ2txaGtpRzl3MEJBUXNGQURBYk1Sa3dGd1lEVlFRS0V4Qm4KWlhSaGJXSmhjM05oWkc5eUxtbHZNQjRYRFRJeE1EWXdPVEEyTVRneE1Wb1hEVEl5TURZd09UQTJNVGd4TVZvdwpRekVaTUJjR0ExVUVDaE1RWjJWMFlXMWlZWE56WVdSdmNpNXBiekVtTUNRR0ExVUVBeE1kWVdkbGJuUXRhVzVxClpXTjBiM0l1WVcxaVlYTnpZV1J2Y2k1emRtTXdnZ0lpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElDRHdBd2dnSUsKQW9JQ0FRQzVDWnowbFEvNjlNWEl2TWtTckZQOElRdEM1YlRlQzRMdUd3TVZPS24wQzUydTl5aEdQYkJKY1I0dQpEZ1l1NmNvSWNIMS91RmJPMVZseGJRR0ptTjVyQXA5MFo1WUZLUSt2SnBobGpVaWFiUVZtbTZWNkgzYmNHalpsCmprYXdhQktIaVlNQ1VvOU9YNnplY0NhOTQ3RlZ1YVIvMFpndkcyNjlNNWVCL25hVExJK09iTHRYUEF4TDNiaE8KN2dxczhWb3c1Q2NDZFRaRk9yekJ3Ui9vZERqdGxKR2tTR2JkN2hXVG9UaUx1VUcrcE1Ea0lmNXlOSm5zQXorNgpQZm1Db2ZhekFuV0dDYTd5T0gvRW1UcDRNTlJKRVQ1NkFvVlVvcDFxbmdJZk1aUlZvNzJuTDAwQkpDNkhRdCt1CnVXSmlXeWJaalBiV3F0UGJFeVRGUEZoUjhLSVY3SVBXTXZ2K3dmYk02Q3JWbVFlVTcyY2E5MmRSY3VnWkVQS2wKVnkrMXZCV2pKaFk5clF1cmNDcW1IVEt6bGZuajRmbmx0SitmQ1dqQVNiTEFCU2hRWTFaN3RKbGVnY3JZZDh4NgpUZ1NyN0pLS1RLWkFVaTRZQlNZYlpWV0tjQVNuaGpSQlJSTWE1UDFjZkI1R1FqZCtyZDBSK05LOFRRanpNWis0CnhPQnJ5aUVCcUFhM0VhbERQZ3Q2RUo1cG5Kd2lxQ3oyNm1oOXg2bHo1MzB5MFQ3Yi80cWlhMjYrWG5WSFdrMlUKcHZyUFlMUG9PUjkxZFdDUkx1OGF2UzVITGtRZzRLbWlIem05SnBzT3JiaE83NFBmc1BQUmVkSVFKN21TckdvVgpSdkZpQjBpYXVvYjR5VzExaVZBUlkzT2ZDa1d6ZFJyZndvMUdMTDZUZk5Za2FkOXZWd0lEQVFBQm80R21NSUdqCk1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBZEJnTlZIU1VFRmpBVUJnZ3JCZ0VGQlFjREFnWUlLd1lCQlFVSEF3RXcKSFFZRFZSME9CQllFRkhjU1ZFTnprb3F2SjhQTXE1c0k5cmhZWExQb01GTUdBMVVkRVFSTU1FcUNEbUZuWlc1MApMV2x1YW1WamRHOXlnaGxoWjJWdWRDMXBibXBsWTNSdmNpNWhiV0poYzNOaFpHOXlnaDFoWjJWdWRDMXBibXBsClkzUnZjaTVoYldKaGMzTmhaRzl5TG5OMll6QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FnRUFZL29icjJRSjViYysKWFF2N0tVMmN2NmZnOStVZHFLSUlaN2lHNGNKN1Fyb1R3cFZLeVlHVFpOUTVyeGNUWkxQN1Z6b21iZ0VsTWVmcwpzanlLUnVOYXZSL2wrYTUreWZIYnd5bHFsK0d3MElRbSt5UFpmeEt4OCtCSzFzZmpIdFJ3Z01uNGVEYkd5M0pEClhGc3FUWHdFT1VqMkNLS0J2M1JXKzhlNUtDOW1GSDhaWi9yZzJuUTRJb1FJOFh3UFlIcXRkenU2c2xpTHIvVWwKY28xZXR6bW5FdVFKVy9RL24rSzQvUlpoUjAzbEVCNlZVZVczUzQ3ZUpScENtN0RrdlV3ZWRYSXBoV1FuYkxNeApDSkd5RTNucjdaL3ZPaDhxYlp6amFqcXNtd01kTTJYbUVVckZ5enE4bnpkZ0JRZnh4YyswUkt5Rm45RnlkTWhYCjFqajhmNjVzbjRTczhUNndNWDA3c2tDMllua3ZMTVplREIvVUcrYUhWTW44ZXROT2RleXhDU3FUZG5mR0pIWDIKaWk3Yi93WmZVaDUvemNCcmdMTHhsaU5COHFBYjVvZ2p2WmNDcjY3SWZMdndkZndBeVdRN3JSekg4ckpWOHVQZwpoZTNMcjVVYnMxZWJsd1dKZ2lpSjNjZmRMV3o3S3BFakxoTkZVdXMwTlNCd0JLUmZWWkpxbXVzYXNsN1VpZEV0CnpTYzdoZ1E2SzZ3aGkyZ1RldEJNL2FaMk5ORVpRSFY1L3NpQUpyR0ZZZncwQ0IvSlFhcFkybDNDUUNRalN1blIKK25mRFQ2ekJGWHovNVZvNGJqOFh3K1VRRzJMY0JvRzZVRU9Udlk0eXJuNTdGeUUzaW9nbUJReTQvaHUzV0dKRgoxdkF3eEZrRXgxUk1BQXBPOTBuT1FPL2RGMnlWMzc4PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n  key.pem: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKSndJQkFBS0NBZ0VBdVFtYzlKVVArdlRGeUx6SkVxeFQvQ0VMUXVXMDNndUM3aHNERlRpcDlBdWRydmNvClJqMndTWEVlTGc0R0x1bktDSEI5ZjdoV3p0VlpjVzBCaVpqZWF3S2ZkR2VXQlNrUHJ5YVlaWTFJbW0wRlpwdWwKZWg5MjNCbzJaWTVHc0dnU2g0bURBbEtQVGwrczNuQW12ZU94VmJta2Y5R1lMeHR1dlRPWGdmNTJreXlQam15NwpWendNUzkyNFR1NEtyUEZhTU9RbkFuVTJSVHE4d2NFZjZIUTQ3WlNScEVobTNlNFZrNkU0aTdsQnZxVEE1Q0grCmNqU1o3QU0vdWozNWdxSDJzd0oxaGdtdThqaC94Sms2ZUREVVNSRStlZ0tGVktLZGFwNENIekdVVmFPOXB5OU4KQVNRdWgwTGZycmxpWWxzbTJZejIxcXJUMnhNa3hUeFlVZkNpRmV5RDFqTDcvc0gyek9ncTFaa0hsTzluR3ZkbgpVWExvR1JEeXBWY3Z0YndWb3lZV1BhMExxM0FxcGgweXM1WDU0K0g1NWJTZm53bG93RW15d0FVb1VHTldlN1NaClhvSEsySGZNZWs0RXEreVNpa3ltUUZJdUdBVW1HMlZWaW5BRXA0WTBRVVVUR3VUOVhId2VSa0kzZnEzZEVmalMKdkUwSTh6R2Z1TVRnYThvaEFhZ0d0eEdwUXo0TGVoQ2VhWnljSXFnczl1cG9mY2VwYytkOU10RSsyLytLb210dQp2bDUxUjFwTmxLYjZ6MkN6NkRrZmRYVmdrUzd2R3IwdVJ5NUVJT0Nwb2g4NXZTYWJEcTI0VHUrRDM3RHowWG5TCkVDZTVrcXhxRlVieFlnZEltcnFHK01sdGRZbFFFV056bndwRnMzVWEzOEtOUml5K2szeldKR25mYjFjQ0F3RUEKQVFLQ0FnQWs1aXJiaDBJNWpFaEl3SVRrYVdNU0cxRFFsVmdkRTNTWG9PRmJnUUk3UFhuRFkxd3ZyYXVTNmJEWQpCRW50WHdlLzZSYk51bWZ0TlJSeUR3ZklkYWljOUZmeDhabzBDejBxYzJyZWpXOFdCSG1ZUForaEc5Y3JDenNmCncyQ0xXeVdleTZoSmRVZEluTUd2VmZRVDErME1LRW9LaHpSdTZHeUw1RmJwUUdKSzlRN25tdjA4NXllWWxXMWsKcUFtTzlVNUVBNnRYelNIMmFrRXI3aVE3eWJsMGZ6VVA2clJBdTNLb0R2Vmt2NXZCdGI4VmYwWHJabHZ2ZWJKaQpwR3MyUnJBWWdId0pMN01vY0dYaVFhQXNTYWg3cmFtazBRM2ZrOUlLYWRvSWVHMlpLbDd1a1BzdEtCYnpPRDI4CjdSRVdoaERZUDBrV0E5amRqaEsrRmt1U2c0Yit1eEJKUzZMemUwVkp1V3dXdHVPSk45YUord1NoMlo1V2NqeisKVzFncTdNQ3VCc1BFTTVMaDFSTkd3eGxKZGJTakU2ejZOalF4Q2R1dVFkMjZHNk1XTElDRFRVcHZDOFM5QXBRZApvMkk1QkxuTHVpWkRjWDdYVGpCbVhNQVBnRzdva3VWUkRtaXI1QXFxQjRWcDNBNWpWandsS2dFcVJUOXlNNDl2Cm1NV212V1NJSVQvdmljMElqZ0pQS1Rpa21BT2ZGQzl3aTNnN1MrQmxKV1lMaWMwc2dLeVUrVnBJYW9senRLU2wKUTI3UExRY2xsNkhkVUMzcmlWVU5aSnBGRFVJM2tLWEgwSldsY0hTQjhGbE9rZGZueXh4QiswVHRXMmFBTkQvUApZaEx4Y0ZMZUlDc3dtN1cvNm5DUW1DN2tlUklsa01IZERwUlprbUFkR0U1dVVIbXNHUUtDQVFFQXhuOUhKcEE0CjBIOXhoR1dWS3ZwdjZ6bmVucmh6d3ZUelBzbGRzR2Qxb3BKRnZtV2M1SWxEdnYrcmliK3VJMWZFdVhyRXhkZjMKS2JDQm51UkpyTGc3ZEYrZ0IyNTBxRmJKblQ5NUdiUzBkeUozSUJTZlFZL1FVZEJjTmxKNW1yL1lTZXIrVUU1SQpPOHFNN0ozWGZxN21SU2p4bVJVNy8yRXZRVmJYQUVrZmc3KysydE9OYjQxR3NpYW1tcDFIMzh4U1VJNUZnV3RBCitPS1NEczhka21IeC9HLzJkWktQWXBic1hMc0ZRWTYzeVcrdzBJTEovc05xUHNJNE8wTU8zZ1RzL0c3WXA0em4KVlRCSnZpQ2RhbC94LzJBRFR5RnNYZld5MFN4aVdWRzlLM3NJUHpkNWdkN3B1d3poeTlDWllaaE9OT24rVmJETwpQOERra0F5TkgzOSsvUUtDQVFFQTdxUW9WQ3NTRkNsbFA1TXVZN1FzR1pkdGU0N1NjRTI1RXlXUTU3OFk3SzdrCi9KL0lZbHJGRnhCMW1senZMUTI1aHh0eUgzTHpkdDVya3BOMmRMTXg4NnZ2d2lIZ2JHakhpOE9YMlRXdzJBS00KTVNJVTREckJjUU85UGMvK2VCaytzbXp6MHhvUXd2ZEN4b2xIZGR4bitYTzJVZDJBemZicHZuV0ZTWTFDT2FscQpCWTNoWUlRd2czUTFscEJuanA1RjZrNks5Y0ZoVVhud1U0UEYvb0JWYXYvRHBOdDJtalFseVdiaFJoNWZqOGNECjVVaG0zVGFJeSt6bEk1dUtsLzVqYWwwaXlIelM0TGR6TFUrREVyd3lIWXdhOWxvTURPcXZ5cUU4QjM1UWJYbTUKclY5RktiSjZmR1N6YlVIMzk2VTI5eU03SzVxL0luNHNjRlViUjFHNTR3S0NBUUJCbmxmR0RzMUpWNmdPTFlxbgpYNHphQlJKc0ErdjQyZ01Ea1l2UVFoTm9QOVNnZ1hUaE0rTmFZNml3YTlaRVJzSjQwblgwTlZXMnlXdkFQQldDCjdKQnpBeDJpOTBmSFVwRnAwMDdVU2FHUzlLak95U0p3aVB0RDRJNXJRczZDY3NNc2hHdTMzbmtRa3dBTlJJeTEKaTFvQ0tPdmRRR0RLSnJWNWN4eTJNbllobHFTZ01HbEVKRDducTlGTnNZck9GL3hxTnU0UlA0U0dBbGhvWHQyVwp3NXc4YUVMZ1VTZm5YcDhhZEpUalBDdlRnb0hBSEV4c1ZPdHRmY1ViQ2lzRENEdlRvMDBwN25HVy90U1I3clE3Ck15YUwxcENoZXhvWTRaMVFlc3kwWEVvZDhwa2lWTk50TkdTdCtpODJzbW5TRW9oZ0E2NlpZMDU3VXVmOEdyb1kKWEl4OUFvSUJBSEZyZSs5aytSYTBCZmNOVU1MNll1dFcyU1ljWDFBWTRKbUZCVFhmMFV6TVl2RGVVRExPeVZXSQo4UndJaDNSMlRYTTFUQ3crU2hCNDdjK2dYbkJncXFFUldzWjlxMWhiQkZ3Yy9oS3lQZmFzWDAwSzBia3dzN2V1CiswWmhrS1FyKzJ4NTgvaWxMc283RW5XaDBXRG0vRlBHOXlRNWpucFZuQXAxZUgvWXIxMFFjOTluNjNJZjRaN1kKb0krSzJtMGlORUNFUys4NWxiTlByVFZFTDlvaHpIY3FVQ1lPV0hRNXpLdklSZEU1cGxtRFVRcXNPcGR3ejl0OApIL3VvZFZxQUFXZ1FFL1FOdjN3bU5JdVllc2R3d3JEZ1lnQXNGQVlmbEtWTnRHWXJWclp4WUJwU2FXREQvd3NZCklWOFFOM1p3QTR1Nkp1azJoeGt4dHBVOUhkWHJ0ZGtDZ2dFQUgyaDJ6QzVqTHVzWnZKRFNteHNpbmd4YTRLSUQKalQ0QVQyNUJ2Wmk1a1ZnUDBobjMrWG1HcVlVY1hSd3ViN2hDeTR3d0RQOFFEWFcvV1pvdXpleUVTQXpMQ2xXbgpKL1FUMEppYUlubUhJS2hKVWowZ2pBY3lCZ0hmeVk0eWJ6aGFFMnFFK2xJRlk5NzJ0ZnI5YUwyOEF0RUhMdjdxClc4SDhYN3JQelV0anVpejB2aXhTTlZLU2FDRHRvWTJXUkN4anVxb3hVOHpZVmFqTVFxZkUwR2NTUE9jVlhlU2YKb3JYN3BNL2pmZk1mMXdHQ3QyZS9hVWNINjZDdGtCTnRpMllUQkl2T0tBdnBPWlZBamxuOThHMUhRM24wK2JHQQpaU0hsdFZuWEZkVkYxZFYwL1NIU0REWUdWcXJwVkhmdjJIdnZmbkdIc3dScjR4cktmTG95SmZpLzRRPT0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K\n"
  },
  {
    "path": "k8s/agent-injector.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: agent-injector\n  namespace: ambassador\nspec:\n  type: ClusterIP\n  selector:\n    app: traffic-manager\n    telepresence: manager\n  ports:\n    - name: agent-injector\n      port: 8443\n      targetPort: https\n---\napiVersion: admissionregistration.k8s.io/v1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: agent-injector-webhook\nwebhooks:\n  - name: agent-injector.telepresence.io\n    clientConfig:\n      service:\n        name: agent-injector\n        namespace: ambassador\n        path: \"/traffic-agent\"\n      caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZGVENDQXYyZ0F3SUJBZ0lFRHY3S3NEQU5CZ2txaGtpRzl3MEJBUXNGQURBYk1Sa3dGd1lEVlFRS0V4Qm4KWlhSaGJXSmhjM05oWkc5eUxtbHZNQjRYRFRJeE1EWXdPVEEyTVRnd09Wb1hEVEl5TURZd09UQTJNVGd3T1ZvdwpHekVaTUJjR0ExVUVDaE1RWjJWMFlXMWlZWE56WVdSdmNpNXBiekNDQWlJd0RRWUpLb1pJaHZjTkFRRUJCUUFECmdnSVBBRENDQWdvQ2dnSUJBTjdrbzROZHMvZTdvaVU0WGM1Rm5DcUlTNDNnZ01Zd2MrR1huaW9zQk50R000K00KMEFiODgweUx2WlRQTURoSVJQbmtlSm9UeThhN3NETVRQcHdtc2tmZFNJRjNRQlVKSlc3ZGNpaWI3Y3lmTzdUWgp2UmM0YW5SSXpYYUtsc1pRQ3ZZNUVLOHJOZ3hDYmd3VGprdldoN01iOUpPdFZqcDMyTEhaUFV5Q1RKQ1diUzRjCm05MHZiWVFOclNPZUpLc0I3bEUrWGVrWnEvODI4OXpBWXBpakYxY3Z3aW1sMU83eUMyc2hKa1Urd0lUcldVSmYKd1J5SDZMVk5zSlZKaXZjWU1EN01panU0T0tPOG1lZkUrYlYxRXpjYVpmWmh4OGowZWNMZjRPdUhQUlBMS2pyVQpjUFFheFRZZm9hdlpKV0RyQU5VSnNEb01BWUZyZkh4VFlmZHZFY2RHaGN0Z1Rpb2xPbUpkenMzYWpwVWpINnhICllOTWRsb2YreHpGSlQzbWxYZ2pKMHJXc2NwQ0lkSG1vVXFWQURpR0huSTJzVEpFMkpneFBodzRYcGQ1OHRxOFQKTFBBZGdsdDF6R2VHeGl5Z3BuU0UyaGppY2grcm1DUlcrT1BReCtMQTdIQTFlTitWZmxpWTJ4S1pDaXlydmdHNApjamlPb2lsR05iOXI5M1ZqT0MrUnpVUWlwRklLVnBqVFc5V1BvQVRTajc3aUJWbmxVWSs3SHpmN2VnS0RNVTBPCjNGRFJOS0Zra1IwTmN1T2dZQTZ3ZmMwbUhJaFpucThVd0pPMTdiRGcrYm5nYUF4RmdJT0Z0ZnJ2U0IybUpXZTEKTkdoSWpkdWt6VkxkbmxjOThDQXhyZHQxbnRlU3hOYnlFTWJvZkxRNTBRUHNHM3htWk5peUMzcFJtV3ZaQWdNQgpBQUdqWVRCZk1BNEdBMVVkRHdFQi93UUVBd0lDaERBZEJnTlZIU1VFRmpBVUJnZ3JCZ0VGQlFjREFnWUlLd1lCCkJRVUhBd0V3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVeEplTitoYjJJdThFYWN6K2FNZVcKUHNiemhZUXdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBTGZESlNxSFZlQ083cWlsaGJ2REhabjBhaW01RTMvMgpyT0FScVE1MFJPZ1hKQy9DM3RrK0Q5NG5JelI1azlMNHBNQ2xSR3prZWQ4bmltRExJMUVPa1hka3czQ0xucXBSCmppam5DV3hOczJIQS9RTGxGNHJZREJXRVk2OFF2OWo3MmFQT1plbTE1d1RXeGswSkd0c05JQ1FkQ08wcjRCWDQKdTAzUFcxVFVnRTBCZS9zNFBuRVM4TVAwdFRacmRxNVk3UDh4c1FxOGEzYjhaR3FYbmdmY3AvOHArVjBQcWhVdQpKdEpoeDhQcjNSMXZDNEFXc1lzTWlQNHZuV3gxVWkrVDUybllGZTNnVHFyc2NPYU9vVUhiOGh0VnI4NGZFOWxRClBIQ2NGTmdQdERPSTBzREgrSmdKQS9Ec3N5MHRZQ2VacGNwUTcybkxPVU5IZzdaUktOa01tVTdua1BmMStBb1cKZTFNY1kycW96dCtwRmZjdGg2d2YzYTNDZEU2ZGtqM1BFd0xrelBxL2lVZTFEbDk4anBYTjJvNGdDcThDS0t1QQo3dFYzekhJZFhwYW53UFVQVEFJQUovSDFPempIZ2ptNUo1NDdBY3dlbUQ4UkFCRTkrSGREYVJtaitCeXd4MFQxCmRDRVNyNm90WThFUVhETGtSQ1hqdnRIM2lORXhHOW9IbjZxL1lZcmtNc2kzNldwSkhpN2lGUmRwMEhXbDBLMmsKeWZFelNRUjR4TE5OWk0zaVBpYXQvZzAyaEEzTGVjbllzU3BMS1RmSFd2T3VTT3kwK1lrR1VsWXQ4Wlgyc2I5cgo5d0dGeFFTQmFrRzNSNkdTMjF2azFCdkpJd2hlcjlBRVowdWtCS282ZS9IcEN4MFFPd2dlempkV1pkUzVvWmdLCmYxczRZaE5PVUVMWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==\n    rules:\n      - operations: [\"CREATE\"]\n        apiGroups: [\"\"]\n        apiVersions: [\"v1\"]\n        resources: [\"pods\"]\n    sideEffects: None\n    admissionReviewVersions: [\"v1\"]\n    timeoutSeconds: 5\n"
  },
  {
    "path": "k8s/apitest.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"apitest\"\nspec:\n  type: ClusterIP\n  selector:\n    service: apitest\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"apitest\"\n  labels:\n    service: apitest\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      service: apitest\n  template:\n    metadata:\n      labels:\n        service: apitest\n    spec:\n      containers:\n        - name: apitest\n          image: localhost:5000/apiserveraccess:latest\n          ports:\n            - containerPort: 8080\n              name: http\n          env:\n            - name: APP_PORT\n              value: \"8080\"\n            - name: LOG_LEVEL\n              value: \"DEBUG\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "k8s/dnsutils-headless.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: dnsutils-headless\nspec:\n  type: ClusterIP\n  clusterIP: None\n  selector:\n    service: dnsutils-headless\n  ports:\n    - port: 8080\n      targetPort: 8080\n---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: dnsutils-headless\n  labels:\n    service: dnsutils-headless\nspec:\n  replicas: 1\n  serviceName: dnsutils-headless\n  selector:\n    matchLabels:\n      service: dnsutils-headless\n  template:\n    metadata:\n      labels:\n        service: dnsutils-headless\n    spec:\n      containers:\n        - name: dnsutils-headless\n          image: gcr.io/kubernetes-e2e-test-images/dnsutils:1.3\n          command:\n            - sleep\n            - \"3600\"\n          imagePullPolicy: IfNotPresent\n      restartPolicy: Always\n"
  },
  {
    "path": "k8s/echo-auto-headless.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: echo-auto-headless\nspec:\n  type: ClusterIP\n  clusterIP: None\n  selector:\n    service: echo-auto-headless\n  ports:\n  - port: 8080\n    targetPort: 8080\n---\napiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: echo-auto-headless\n  labels:\n    service: echo-auto-headless\nspec:\n  replicas: 1\n  serviceName: echo-auto-headless\n  selector:\n    matchLabels:\n      service: echo-auto-headless\n  template:\n    metadata:\n      labels:\n        service: echo-auto-headless\n      annotations:\n        telepresence.io/inject-traffic-agent: enabled\n        telepresence.io/inject-service-ports: \"8080\"\n    spec:\n      containers:\n        - name: echo-auto-headless\n          image: jmalloc/echo-server\n          ports:\n            - containerPort: 8080\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n\n"
  },
  {
    "path": "k8s/echo-double-one.yaml",
    "content": "# The echo-double-one service exposes two ports, 80 and 81 and directs them to one single container\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-double-one\"\nspec:\n  type: ClusterIP\n  selector:\n    service: echo-double-one\n  ports:\n    - name: http\n      port: 80\n      targetPort: http\n    - name: extra\n      port: 81\n      targetPort: extra\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-double-one\"\n  labels:\n    service: echo-double-one\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      service: echo-double-one\n  template:\n    metadata:\n      labels:\n        service: echo-double-one\n    spec:\n      containers:\n        - name: echo-double\n          image: multi:5000/tel2/echo\n          ports:\n            - containerPort: 8080\n              name: http\n            - containerPort: 8081\n              name: extra\n          env:\n            - name: PORTS\n              value: \"8080,8081\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "k8s/echo-double.yaml",
    "content": "# The echo-double service exposes two ports, 80 and 81 and directs them to two separate containers\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-double\"\nspec:\n  type: ClusterIP\n  selector:\n    service: echo-double\n  ports:\n    - name: http\n      port: 80\n      targetPort: http\n    - name: extra\n      port: 81\n      targetPort: extra\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-double\"\n  labels:\n    service: echo-double\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      service: echo-double\n  template:\n    metadata:\n      labels:\n        service: echo-double\n    spec:\n      containers:\n        - name: echo-one\n          image: ghcr.io/telepresenceio/echo-server\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n        - name: echo-two\n          image: ghcr.io/telepresenceio/echo-server\n          ports:\n            - containerPort: 8081\n              name: extra\n          env:\n            - name: PORT\n              value: \"8081\"\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "k8s/echo-sc.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-sc\"\nspec:\n  type: ClusterIP\n  selector:\n    service: echo-sc\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-sc\"\n  labels:\n    service: echo-sc\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      service: echo-sc\n  template:\n    metadata:\n      labels:\n        service: echo-sc\n    spec:\n      securityContext:\n        fsGroup: 1000\n        runAsUser: 1000\n      containers:\n        - name: echo-sc\n          image: ghcr.io/telepresenceio/echo-server:latest\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "k8s/ext-example.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: ext-example\nspec:\n  type: ExternalName\n  externalName: example.com\n"
  },
  {
    "path": "k8s/hello-w-volume.yaml",
    "content": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: hello\n  labels:\n    app: hello\ndata:\n  index.html: |\n    <html>\n      <body>\n        <p id=\"hello\"></p>\n        <script>\n          document.getElementById(\"hello\").innerHTML = \"Hello from \" + navigator.location.hostname + \"!\";\n        </script>\n      </body>\n    </html>\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: hello\nspec:\n  type: ClusterIP\n  selector:\n    service: hello\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: hello\n  labels:\n    service: hello\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      service: hello\n  template:\n    metadata:\n      labels:\n        service: hello\n    spec:\n      volumes:\n        - name: hello-cm-volume\n          configMap:\n            name: hello\n        - name: tmp-volume\n          emptyDir: {}\n      containers:\n        - name: hello-container\n          image: nginx\n          ports:\n            - containerPort: 80\n              name: http\n          volumeMounts:\n            - mountPath: \"/usr/share/nginx/html\"\n              name: hello-cm-volume\n            - mountPath: \"/tmp\"\n              name: tmp-volume\n"
  },
  {
    "path": "k8s/local-echo-easy.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-easy\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-easy\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-easy\"\n  labels:\n    app: echo-easy\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-easy\n  template:\n    metadata:\n      labels:\n        app: echo-easy\n    spec:\n      containers:\n        - name: echo-easy\n          image: localhost:5000/echo-server:latest\n          imagePullPolicy: Always\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "k8s/local-echo-next.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: \"echo-next\"\nspec:\n  type: ClusterIP\n  selector:\n    app: echo-next\n  ports:\n    - name: proxied\n      port: 80\n      targetPort: http\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: \"echo-next\"\n  labels:\n    app: echo-next\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: echo-next\n  template:\n    metadata:\n      labels:\n        app: echo-next\n    spec:\n      containers:\n        - name: echo-next\n          image: localhost:5000/echo-server:latest\n          imagePullPolicy: Always\n          ports:\n            - containerPort: 8080\n              name: http\n          resources:\n            limits:\n              cpu: 50m\n              memory: 128Mi\n"
  },
  {
    "path": "k8s/manager.yaml",
    "content": "---\napiVersion: v1\nkind: Service\nmetadata:\n  name: traffic-manager\nspec:\n  type: ClusterIP\n  clusterIP: None\n  selector:\n    app: traffic-manager\n    telepresence: manager\n  ports:\n  - name: api\n    port: 8081\n    targetPort: api\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: traffic-manager\n  labels:\n    app: traffic-manager\n    telepresence: manager\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: traffic-manager\n      telepresence: manager\n  template:\n    metadata:\n      labels:\n        app: traffic-manager\n        telepresence: manager\n    spec:\n      containers:\n      - name: traffic-manager\n        image: ko://github.com/telepresenceio/telepresence/v2/cmd/traffic\n        ports:\n        - name: api\n          containerPort: 8081\n      restartPolicy: Always\n"
  },
  {
    "path": "k8s/minikube-registry.yaml",
    "content": "apiVersion: v1\nkind: ReplicationController\nmetadata:\n  name: kube-registry-ctl\n  namespace: kube-system\n  labels:\n    k8s-app: kube-registry\n    controller: yep\nspec:\n  replicas: 1\n  selector:\n    k8s-app: kube-registry\n    controller: yep\n  template:\n    metadata:\n      labels:\n        k8s-app: kube-registry\n        controller: yep\n    spec:\n      containers:\n        - name: registry\n          image: registry:2\n          resources:\n            # keep request = limit to keep this container in guaranteed class\n            limits:\n              cpu: 100m\n              memory: 100Mi\n            requests:\n              cpu: 100m\n              memory: 100Mi\n          env:\n            - name: REGISTRY_HTTP_ADDR\n              value: :5000\n            - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY\n              value: /var/lib/registry\n          volumeMounts:\n            - name: image-store\n              mountPath: /var/lib/registry\n          ports:\n            - containerPort: 5000\n              name: registry\n              protocol: TCP\n      volumes:\n        - name: image-store\n          hostPath:\n            path: /data/registry/\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: kube-registry\n  namespace: kube-system\n  labels:\n    k8s-app: kube-registry\nspec:\n  selector:\n    k8s-app: kube-registry\n  ports:\n    - name: registry\n      port: 5000\n      protocol: TCP\n---\napiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: kube-registry-proxy\n  namespace: kube-system\n  labels:\n    k8s-app: kube-registry\n    kubernetes.io/cluster-service: \"true\"\nspec:\n  selector:\n    matchLabels:\n      k8s-app: kube-registry-pod\n  template:\n    metadata:\n      labels:\n        k8s-app: kube-registry-pod\n    spec:\n      containers:\n        - name: kube-registry-proxy\n          image: gcr.io/google_containers/kube-registry-proxy:0.4\n          resources:\n            limits:\n              cpu: 100m\n              memory: 50Mi\n          env:\n            - name: REGISTRY_HOST\n              value: kube-registry.kube-system.svc.cluster.local\n            - name: REGISTRY_PORT\n              value: \"5000\"\n          ports:\n            - name: registry\n              containerPort: 80\n              hostPort: 5000"
  },
  {
    "path": "k8s/private-reg-proxy.yaml",
    "content": "apiVersion: apps/v1\nkind: DaemonSet\nmetadata:\n  name: private-registry-proxy\n  labels:\n   app: private-registry-proxy\nspec:\n  selector:\n    matchLabels:\n      app: private-registry-proxy\n  template:\n    metadata:\n      labels:\n        app: private-registry-proxy\n    spec:\n      containers:\n        - name: tcp-proxy\n          image: quay.io/bentoml/proxy-to-service:v2\n          args:\n            - tcp\n            - \"5000\"\n            - docker-registry.default.svc.cluster.local\n          ports:\n            - containerPort: 5000\n              hostPort: 5000\n              name: tcp\n              protocol: TCP\n"
  },
  {
    "path": "k8s/rs-echo-svc2.yaml",
    "content": "---\n# Use a second service here that appoints the same port in the replicaset to\n# make things a bit more complex for the discovery mechanism, and also force\n# use of the --service flag when intercepting\napiVersion: v1\nkind: Service\nmetadata:\n  name: rs-echo-canary\nspec:\n  type: ClusterIP\n  selector:\n    service: rs-echo\n  ports:\n    - name: http\n      port: 80\n      targetPort: 8080\n"
  },
  {
    "path": "packaging/artifacthub-repo.yml",
    "content": "repositoryID: 37605942-af09-4135-bd75-5241c570ad76\nowners:\n  - name: Thomas Hallgren\n    email: thomas@tada.se\n"
  },
  {
    "path": "packaging/bundle.wxs.in",
    "content": "﻿<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">\n  <Bundle Name=\"Telepresence\" Manufacturer=\"Telepresence Community\" Version=\"TELEPRESENCE_VERSION\"\n    UpgradeCode=\"82fd5464-dd21-44bd-a44d-053cea4da740\">\n\n    <BootstrapperApplication>\n      <bal:WixStandardBootstrapperApplication Theme=\"hyperlinkSidebarLicense\"\n        LicenseUrl=\"https://www.apache.org/licenses/LICENSE-2.0\"\n        xmlns:bal=\"http://wixtoolset.org/schemas/v4/wxs/bal\" LogoSideFile=\"sidebar.png\"\n        SuppressOptionsUI=\"yes\" ShowVersion=\"yes\" />\n    </BootstrapperApplication>\n\n    <Chain>\n      <MsiPackage SourceFile=\"telepresence.msi\" />\n      <MsiPackage SourceFile=\"winfsp.msi\" />\n      <MsiPackage SourceFile=\"sshfs-win.msi\" />\n    </Chain>\n  </Bundle>\n\n</Wix>"
  },
  {
    "path": "packaging/helmpackage.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/blang/semver/v4\"\n\n\ttelcharts \"github.com/telepresenceio/telepresence/v2/charts\"\n)\n\ntype sv semver.Version\n\nfunc (v *sv) String() string {\n\treturn (*semver.Version)(v).String()\n}\n\nfunc (v *sv) Set(s string) error {\n\tver, err := semver.Parse(s)\n\tif err == nil {\n\t\t*v = sv(ver)\n\t}\n\treturn err\n}\n\nfunc main() {\n\tvar output string\n\tvar version sv\n\tflag.StringVar(&output, \"o\", \"\", \"output file\")\n\tflag.Var(&version, \"v\", \"Helm chart version\")\n\tflag.Parse()\n\terr := packageHelmChart(output, semver.Version(version))\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc packageHelmChart(filename string, version semver.Version) error {\n\tfh, err := os.Create(filename)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer fh.Close()\n\treturn telcharts.WriteChart(telcharts.DirTypeTelepresence, fh, telcharts.TelepresenceChartName, version)\n}\n"
  },
  {
    "path": "packaging/homebrew-oss-formula.rb",
    "content": "# This script is generated automatically by the release automation code in the\n# Telepresence repository:\nclass __FORMULA_NAME__ < Formula\n  desc \"Local dev environment attached to a remote Kubernetes cluster\"\n  homepage \"https://telepresence.io\"\n  version \"__NEW_VERSION__\"\n\n  BASE_URL = \"https://github.com/telepresenceio/telepresence/releases/download\"\n  ARCH = Hardware::CPU.arm? ? \"arm64\" : \"amd64\"\n  OPERATING_SYSTEM = OS.mac? ? \"darwin\" : \"linux\"\n  PACKAGE_NAME = \"telepresence-#{OPERATING_SYSTEM}-#{ARCH}\"\n\n  url \"#{BASE_URL}/v#{version}/#{PACKAGE_NAME}\"\n\n  sha256 \"__TARBALL_HASH_DARWIN_AMD64__\" if OS.mac? && Hardware::CPU.intel?\n  sha256 \"__TARBALL_HASH_DARWIN_ARM64__\" if OS.mac? && Hardware::CPU.arm?\n  sha256 \"__TARBALL_HASH_LINUX_AMD64__\" if OS.linux? && Hardware::CPU.intel?\n  # TODO support linux arm64\n  #sha256 \"__TARBALL_HASH_LINUX_ARM64__\" if OS.linux? && Hardware::CPU.arm?\n\n  conflicts_with \"telepresence\"\n\n  def install\n      bin.install \"#{PACKAGE_NAME}\" => \"telepresence\"\n  end\n\n  test do\n      system \"#{bin}/telepresence\", \"--help\"\n  end\nend\n"
  },
  {
    "path": "packaging/homebrew-package.sh",
    "content": "#!/bin/bash\nset -e\n\nif [ -z \"$1\" ]\nthen\n   echo \"Must set version\"\n   exit 1\nfi\n\nVERSION=\"${1}\"\nGITHUB_USER=\"${2:-$(git config get user.name)}\"\nGITHUB_EMAIL=\"${3:-$(git config get user.email)}\"\nGITHUB_TOKEN=\"${4}\"\n\nARCH=(amd64 arm64)\nOS=(darwin linux)\n\nMY_PATH=$(dirname \"$0\")\nMY_PATH=$( cd \"$MY_PATH\" && pwd )\n\nWORK_DIR=\"$(mktemp -d)\"\ncd \"${WORK_DIR}\"\necho \"Working in ${WORK_DIR}\"\n\nBUILD_HOMEBREW_DIR=homebrew\nFORMULA_NAME=\"TelepresenceOss\"\nFORMULA_FILE=\"${MY_PATH}/homebrew-oss-formula.rb\"\nFORMULA=\"Formula/telepresence-oss.rb\"\n\nfor this_os in \"${OS[@]}\"; do\n    for this_arch in \"${ARCH[@]}\"; do\n\n        if [ \"${this_arch}\" == \"arm64\" ] && [ \"${this_os}\" == \"linux\" ]; then\n            # TODO support linux arm64\n            continue\n        fi\n\n        # We should only be updating homebrew with a version of telepresence that\n        # already exists, so let's download it\n        DOWNLOAD_PATH=\"/download/v${VERSION}/telepresence-${this_os}-${this_arch}\"\n        echo \"Downloading ${DOWNLOAD_PATH}\"\n        mkdir -p \"${WORK_DIR}/${this_os}/${this_arch}/\"\n        curl -fL \"https://github.com/telepresenceio/telepresence/releases${DOWNLOAD_PATH}\" -o \"${WORK_DIR}/${this_os}/${this_arch}/telepresence\"\n        declare -x \"TARBALL_HASH_${this_os}_${this_arch}\"=\"$(shasum -a 256 \"${WORK_DIR}/${this_os}/${this_arch}/telepresence\" | cut -f 1 -d \" \")\"\n        tmp_var=TARBALL_HASH_${this_os}_${this_arch}\n        echo \"${tmp_var} == ${!tmp_var}\"\n    done\ndone\n\nexport HASH_ERRORS=0\n\nfor this_os in \"${OS[@]}\"; do\n    for this_arch in \"${ARCH[@]}\"; do\n\n        if [ \"${this_arch}\" == \"arm64\" ] && [ \"${this_os}\" == \"linux\" ]; then\n            # TODO support linux arm64\n            continue\n        fi\n\n        # We don't want to update our homebrew formula if there\n        # isn't a hash, so exit early if that's the case.\n        tmp_var=\"TARBALL_HASH_${this_os}_${this_arch}\"\n        if [ -n \"${!tmp_var}\" ]; then\n            echo \"Telepresence binary hash: ${tmp_var} == ${!tmp_var}\"\n        else\n            echo \"Telepresence binary could not be hashed: ${tmp_var}\"\n            HASH_ERRORS=$((HASH_ERRORS++))\n        fi\n    done\ndone\n\necho \"HASH_ERRORS==${HASH_ERRORS}\"\n\nif [ \"${HASH_ERRORS}\" -gt 0 ]; then\n    exit 1\nfi\n\nexport GIT_CONFIG_GLOBAL=/dev/null\nexport GIT_CONFIG_SYSTEM=/dev/null\n\n# Clone telepresenceio-homebrew:\necho \"Cloning into ${BUILD_HOMEBREW_DIR}...\"\nif [ \"${GITHUB_TOKEN}\" == \"\" ]; then\n  git clone \"https://github.com/telepresenceio/homebrew-telepresence.git\" \"${BUILD_HOMEBREW_DIR}\"\nelse\n  git clone \"https://${GITHUB_TOKEN}@github.com/telepresenceio/homebrew-telepresence.git\" \"${BUILD_HOMEBREW_DIR}\"\nfi\ncd \"${BUILD_HOMEBREW_DIR}\"\n\n# Update recipe\nmkdir -p \"$(dirname \"${FORMULA}\")\"\ncp \"${FORMULA_FILE}\" \"${FORMULA}\"\n\nsed -i'' -e \"s/__FORMULA_NAME__/${FORMULA_NAME}/g\" \"${FORMULA}\"\nsed -i'' -e \"s/__NEW_VERSION__/${VERSION}/g\" \"${FORMULA}\"\n\nfor this_os in \"${OS[@]}\"; do\n    for this_arch in \"${ARCH[@]}\"; do\n\n        if [ \"${this_arch}\" == \"arm64\" ] && [ \"${this_os}\" == \"linux\" ]; then\n            # TODO support linux arm64\n            continue\n        fi\n        tmp_var=\"TARBALL_HASH_${this_os}_${this_arch}\"\n        sed -i'' -e \"s/__TARBALL_HASH_${this_os^^}_${this_arch^^}__/${!tmp_var}/g\" \"${FORMULA}\"\n    done\ndone\n\nchmod 644 \"${FORMULA}\"\n\n# Use the correct machine user for committing\ngit config --local user.email \"${GITHUB_EMAIL}\"\ngit config --local user.name \"${GITHUB_USER}\"\n\ngit add \"${FORMULA}\"\ngit commit -m \"Release ${VERSION}\"\n\n# This cat is just so we can see the formula in case\n# the git permissions are incorrect and we can't publish\n# the change. Once we know the automation is working, we can\n# remove it.\ncat \"${FORMULA}\"\n\ngit tag --message \"Release ${VERSION}\" \"${VERSION}\"\ngit push origin \"${VERSION}\" main\n\n# Clean up the working directory\nrm -rf \"${WORK_DIR}\"\n"
  },
  {
    "path": "packaging/install-telepresence.ps1",
    "content": "#Requires -RunAsAdministrator\n\nparam\n(\n    $Path = \"$env:ProgramFiles\\telepresence\"\n)\n\n$current_directory = (Get-Location).path\n\necho \"Installing telepresence to $Path\"\n\nStart-Process msiexec -Wait -verb runAs -Args \"/i $current_directory\\winfsp.msi /passive /qn /L*V winfsp-install.log\"\nStart-Process msiexec -Wait -verb runAs -Args \"/i $current_directory\\sshfs-win.msi /passive /qn /L*V sshfs-win-install.log\"\n\nif(!(test-path $Path))\n{\n    New-Item -ItemType Directory -Force -Path $Path\n}\n\nCopy-Item \"telepresence.exe\" -Destination \"$Path\" -Force\nCopy-Item \"wintun.dll\" -Destination \"$Path\" -Force\n\n# Update PATH if entries do not exist only\n$currentPath = [Environment]::GetEnvironmentVariable(\"Path\", \"Machine\")\n@(\"$Path\", \"C:\\Program Files\\SSHFS-Win\\bin\") | Where-Object { $currentPath -notlike \"*$_*\" } | ForEach-Object { $currentPath = \"$_;$currentPath\" }\n[Environment]::SetEnvironmentVariable(\"Path\", $currentPath, \"Machine\")\n\necho \"Telepresence installed to $Path\""
  },
  {
    "path": "packaging/telepresence.wxs.in",
    "content": "﻿<Wix xmlns=\"http://wixtoolset.org/schemas/v4/wxs\">\n  <Package Name=\"Telepresence\" Language=\"1033\" Codepage=\"1252\" Version=\"TELEPRESENCE_VERSION\"\n    Manufacturer=\"Telepresence Community\" InstallerVersion=\"100\" ProductCode=\"*\"\n    UpgradeCode=\"fdac1021-3405-4097-84e3-1e683ba9eee5\">\n    <SummaryInformation Keywords=\"Installer\" Description=\"Telepresence Installer\"\n      Manufacturer=\"Telepresence Community\" />\n\n    <Media Id=\"1\" Cabinet=\"Sample.cab\" EmbedCab=\"yes\" DiskPrompt=\"CD-ROM #1\" />\n    <Property Id=\"DiskPrompt\" Value=\"Telepresence Installer\" />\n\n    <Feature Id=\"Complete\" Level=\"1\">\n      <ComponentRef Id=\"MainExecutable\" />\n      <ComponentRef Id=\"WintunLibrary\" />\n      <ComponentRef Id=\"ProgramMenuDir\" />\n      <ComponentRef Id=\"sshfsENV\" />\n    </Feature>\n\n    <StandardDirectory Id=\"ProgramFilesFolder\">\n      <Directory Id=\"TelepresenceOSS\" Name=\"TelepresenceOSS\">\n        <Directory Id=\"INSTALLDIR\" Name=\"Telepresence\">\n\n          <Component Id=\"MainExecutable\" Guid=\"*\">\n            <File Id=\"TelepresenceEXE\" Name=\"telepresence.exe\" DiskId=\"1\" Source=\"telepresence.exe\"\n              KeyPath=\"yes\">\n              <Shortcut Id=\"startmenuFoobar10\" Directory=\"ProgramMenuDir\" Name=\"Telepresence\"\n                WorkingDirectory=\"INSTALLDIR\" Advertise=\"yes\" />\n            </File>\n\n            <Environment\n              Id=\"telePATH\"\n              Name=\"PATH\"\n              Value=\"[INSTALLDIR]\"\n              Part=\"last\"\n              Action=\"set\"\n              System=\"yes\" />\n          </Component>\n\n          <Component Id=\"WintunLibrary\" Guid=\"*\">\n            <File Id=\"WintunDLL\" Name=\"wintun.dll\" DiskId=\"1\" Source=\"wintun.dll\" KeyPath=\"yes\" />\n          </Component>\n\n          <!--HACK\n          add sshfs to PATH-->\n          <Component Id=\"sshfsENV\" Guid=\"37f61466-6207-44ba-a2ae-d3a712b1e10a\">\n            <Environment\n              Id=\"sshfsPATH\"\n              Name=\"PATH\"\n              Value=\"C:\\Program Files\\SSHFS-Win\\bin\"\n              Part=\"last\"\n              Action=\"set\"\n              System=\"yes\" />\n          </Component>\n\n        </Directory>\n      </Directory>\n    </StandardDirectory>\n\n    <StandardDirectory Id=\"ProgramMenuFolder\">\n      <Directory Id=\"ProgramMenuDir\" Name=\"Telepresence\">\n\n        <Component Id=\"ProgramMenuDir\" Guid=\"*\">\n          <RemoveFolder Id=\"ProgramMenuDir\" On=\"uninstall\" />\n          <RegistryValue Root=\"HKCU\" Key=\"Software\\[Manufacturer]\\[ProductName]\" Type=\"string\"\n            Value=\"\" KeyPath=\"yes\" />\n        </Component>\n\n      </Directory>\n    </StandardDirectory>\n\n  </Package>\n</Wix>"
  },
  {
    "path": "packaging/windows-package.sh",
    "content": "#!/bin/bash\nset -e -x\n\n# This is a scrappy first attempt at a windows \"installer\".\n# It generates a zip file with all the dependencies and things required\n# for running telepresence in windows. We should eventually change this\n# to produce a msi, but for developer preview this is likely fine.\n\nif [ -z \"$TELEPRESENCE_VERSION\" ]\nthen\n   echo \"Must set version\"\n   exit 1\nfi\n\nif [ -z \"$GOARCH\" ]\nthen\n   echo \"Must set GOARCH\"\n   exit 1\nfi\n\nSCRIPT_DIR=$( dirname -- \"${BASH_SOURCE[0]}\")\n\nWINFSP_VERSION=1.11.22176\nSSHFS_WIN_VERSION=3.7.21011\nWINTUN_VERSION=0.14.1\n# SHA2-256 Checksum for wintun-0.14.1.zip\nWINTUN_CHECKSUM=07c256185d6ee3652e09fa55c0b673e2624b565e02c4b9091c79ca7d2f24ef51\nBINDIR=\"${BINDIR:-./build-output/bin}\"\n\nrm -f \"${BINDIR}/telepresence.zip\"\nrm -f \"${BINDIR}/telepresence-setup.exe\"\n\nZIPDIR=\"${ZIPDIR:-$BINDIR/telepresence-windows}\"\nrm -rf \"$ZIPDIR\"\nmkdir -p \"$ZIPDIR\"\n\nif [[ ! \"${ZIPDIR}\" ]]; then\n    echo \"Could not create $ZIPDIR for windows package\"\n    exit 1\nfi\n\n# Download sshfs-win.msi + winfsp.msi\n# ${WINFSP_VERSION%.*} will remove the last `.` and everything after it\ncurl -L -o \"${ZIPDIR}/winfsp.msi\" \"https://github.com/billziss-gh/winfsp/releases/download/v${WINFSP_VERSION%.*}/winfsp-${WINFSP_VERSION}.msi\"\ncurl -L -o \"${ZIPDIR}/sshfs-win.msi\" \"https://github.com/billziss-gh/sshfs-win/releases/download/v${SSHFS_WIN_VERSION}/sshfs-win-${SSHFS_WIN_VERSION}-x64.msi\"\n\n# Download wintun\ncurl -L -o \"${BINDIR}/wintun.zip\" \"https://www.wintun.net/builds/wintun-${WINTUN_VERSION}.zip\"\necho \"${WINTUN_CHECKSUM}  ${BINDIR}/wintun.zip\" | sha256sum -c -\nunzip -p -C \"${BINDIR}/wintun.zip\" \"wintun/bin/${GOARCH}/wintun.dll\" > \"${ZIPDIR}/wintun.dll\"\n\ncp \"${BINDIR}/telepresence.exe\" \"${ZIPDIR}/telepresence.exe\"\n\n# Copy powershell install script into $ZIPDIR\ncp \"${SCRIPT_DIR}/install-telepresence.ps1\" \"${ZIPDIR}/install-telepresence.ps1\"\n\npowershell -Command \"Compress-Archive -Path '${ZIPDIR}/*' -DestinationPath '${BINDIR}/telepresence.zip'\"\n\n# Generate installer\ncp \"${SCRIPT_DIR}/sidebar.png\" \"${ZIPDIR}/sidebar.png\"\nTELEPRESENCE_PLAIN_VERSION=${TELEPRESENCE_VERSION#v}\nTELEPRESENCE_PLAIN_VERSION=${TELEPRESENCE_PLAIN_VERSION%-*}\nsed s/TELEPRESENCE_VERSION/\"$TELEPRESENCE_PLAIN_VERSION\"/ \"${SCRIPT_DIR}/telepresence.wxs.in\" > \"${ZIPDIR}/telepresence.wxs\"\nsed s/TELEPRESENCE_VERSION/\"$TELEPRESENCE_PLAIN_VERSION\"/ \"${SCRIPT_DIR}/bundle.wxs.in\" > \"${ZIPDIR}/bundle.wxs\"\n\nWIX_VERSION=4.0.4\ndotnet tool install --global wix --version $WIX_VERSION\n\ncd \"${ZIPDIR}\"\nwix build -o telepresence.msi telepresence.wxs\nwix extension add -g WixToolset.Bal.wixext/$WIX_VERSION\nwix build -ext WixToolset.Bal.wixext/$WIX_VERSION -o \"${BINDIR}/telepresence-setup.exe\" bundle.wxs\n\nrm -rf \"${ZIPDIR}\"\n"
  },
  {
    "path": "pkg/agentconfig/container.go",
    "content": "package agentconfig\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype ContainerBuilder struct {\n\tMountPolicies types.MountPolicies\n\tPod           *core.PodTemplateSpec\n\tConfig        *Sidecar\n}\n\n// AgentContainer will return a configured traffic-agent.\nfunc (a *ContainerBuilder) AgentContainer(ctx context.Context) (*core.Container, map[string]string, error) {\n\tports := make([]core.ContainerPort, 0, 5)\n\tconfCns := a.configuredContainers(ctx)\n\n\tnames := make(map[string]int)\n\ta.eachConfiguredContainer(confCns, func(app *core.Container, cc *Container) {\n\t\tswitch cc.Replace {\n\t\tcase ReplacePolicyContainer:\n\t\t\t// Simply inherit the ports of the replaced container\n\t\t\tports = append(ports, app.Ports...)\n\t\tcase ReplacePolicyIntercept:\n\t\t\tfor _, ic := range PortUniqueIntercepts(cc) {\n\t\t\t\tname := ic.ContainerPortName\n\n\t\t\t\t// We don't want to apply duplication logic to empty strings\n\t\t\t\t// as - is not a valid starting character for port names.\n\t\t\t\tif name != \"\" {\n\t\t\t\t\tif n, ok := names[name]; ok {\n\t\t\t\t\t\t// if name already exists, append a number to it\n\n\t\t\t\t\t\tn++\n\t\t\t\t\t\tnames[name] = n\n\n\t\t\t\t\t\t// convert to numeric name suffix\n\t\t\t\t\t\tsuffix := \"-\" + strconv.Itoa(n)\n\t\t\t\t\t\t// if string length of name plus number is greater than 15\n\t\t\t\t\t\tif len(name)+len(suffix) > 15 {\n\t\t\t\t\t\t\t// truncate name to 15 characters\n\t\t\t\t\t\t\tname = name[:15-len(suffix)]\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tname += suffix\n\t\t\t\t\t} else {\n\t\t\t\t\t\tnames[name] = 1\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tports = append(ports, core.ContainerPort{\n\t\t\t\t\tName:          name,\n\t\t\t\t\tContainerPort: int32(ic.AgentPort),\n\t\t\t\t\tProtocol:      core.Protocol(ic.Protocol.String()),\n\t\t\t\t})\n\t\t\t}\n\t\tdefault:\n\t\t}\n\t})\n\n\tevs := make([]core.EnvVar, 0, len(a.Config.Containers)*5)\n\tefs := make([]core.EnvFromSource, 0, len(a.Config.Containers)*3)\n\ta.eachConfiguredContainer(confCns, func(app *core.Container, cc *Container) {\n\t\tevs = appendAppContainerEnv(app, cc, evs)\n\t\tefs = appendAppContainerEnvFrom(app, cc, efs)\n\t})\n\tif a.Config.APIPort > 0 {\n\t\tevs = append(evs, core.EnvVar{\n\t\t\tName:  EnvAPIPort,\n\t\t\tValue: strconv.Itoa(int(a.Config.APIPort)),\n\t\t})\n\t}\n\tevs = append(evs,\n\t\tcore.EnvVar{\n\t\t\tName: EnvPrefixAgent + \"POD_IP\",\n\t\t\tValueFrom: &core.EnvVarSource{\n\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\tFieldPath:  \"status.podIP\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tcore.EnvVar{\n\t\t\tName: EnvPrefixAgent + \"POD_UID\",\n\t\t\tValueFrom: &core.EnvVarSource{\n\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\tFieldPath:  \"metadata.uid\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tcore.EnvVar{\n\t\t\tName: EnvPrefixAgent + \"NAME\",\n\t\t\tValueFrom: &core.EnvVarSource{\n\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\tFieldPath:  \"metadata.name\",\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\n\tmounts := make([]core.VolumeMount, 0, len(a.Config.Containers)*3)\n\ta.eachConfiguredContainer(confCns, func(app *core.Container, cc *Container) {\n\t\tmounts = a.appendVolumeMounts(app, cc, mounts)\n\t})\n\tmounts = append(mounts,\n\t\tcore.VolumeMount{\n\t\t\tName:      PodInfoVolumeName,\n\t\t\tMountPath: PodInfoMountPath,\n\t\t},\n\t\tcore.VolumeMount{\n\t\t\tName:      ExportsVolumeName,\n\t\t\tMountPath: ExportsMountPoint,\n\t\t},\n\t\tcore.VolumeMount{\n\t\t\tName:      TempVolumeName,\n\t\t\tMountPath: TempMountPoint,\n\t\t},\n\t)\n\n\tanns := make(map[string]string)\n\tvar err error\n\tmounts, err = a.mountSecrets(annotation.DownstreamTLSSecret, annotation.DownstreamCertificatePath, anns, mounts)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tmounts, err = a.mountSecrets(annotation.UpstreamTLSSecret, annotation.UpstreamCertificatePath, anns, mounts)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif len(efs) == 0 {\n\t\tefs = nil\n\t}\n\n\ta.eachConfiguredContainer(confCns, func(app *core.Container, cc *Container) {\n\t\tif cc.Replace == ReplacePolicyContainer {\n\t\t\tcnJson, err := json.Marshal(app)\n\t\t\tif err != nil {\n\t\t\t\tclog.Errorf(ctx, \"unable to marshal container %s.%s/%s to json: %v\", a.Config.WorkloadName, a.Config.Namespace, app.Name, err)\n\t\t\t}\n\t\t\tanns[annotation.ReplaceAnnotationKey(cc.Name)] = string(cnJson)\n\t\t}\n\t})\n\n\tcfg, _ := MarshalTight(a.Config)\n\tanns[annotation.Config] = cfg\n\n\tif len(ports) == 0 {\n\t\tports = nil\n\t}\n\tac := &core.Container{\n\t\tName:         ContainerName,\n\t\tImage:        a.Config.AgentImage,\n\t\tArgs:         []string{\"agent\"},\n\t\tPorts:        ports,\n\t\tEnv:          evs,\n\t\tEnvFrom:      efs,\n\t\tVolumeMounts: mounts,\n\t\tReadinessProbe: &core.Probe{\n\t\t\tProbeHandler: core.ProbeHandler{\n\t\t\t\tExec: &core.ExecAction{\n\t\t\t\t\tCommand: []string{\"/bin/stat\", \"/tmp/agent/ready\"},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tImagePullPolicy: core.PullPolicy(a.Config.PullPolicy),\n\t}\n\tif r := a.Config.Resources; r != nil {\n\t\tac.Resources = *r\n\t}\n\n\tappSc := a.Config.SecurityContext\n\tif appSc == nil {\n\t\t// Assign the security context of the first container to the traffic agent.\n\t\tappSc, err = a.firstAppSecurityContext()\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\tac.SecurityContext = appSc\n\n\treturn ac, anns, nil\n}\n\nfunc (a *ContainerBuilder) mountSecrets(annotation, certPath string, anns map[string]string, mounts []core.VolumeMount) ([]core.VolumeMount, error) {\n\tsecretsAndPorts, err := annotationPrefixedPorts(a.Pod.Annotations, annotation)\n\tif err != nil || len(secretsAndPorts) == 0 {\n\t\treturn mounts, err\n\t}\n\tfor _, secret := range maps.SortedKeys(secretsAndPorts) {\n\t\tvolName := fmt.Sprintf(\"%s-vol\", secret)\n\t\tvolPath := fmt.Sprintf(\"%s/%s\", DownstreamTLSVolumePath, secret)\n\t\tmounts = append(mounts, core.VolumeMount{\n\t\t\tName:      volName,\n\t\t\tMountPath: volPath,\n\t\t})\n\t\tfor _, p := range secretsAndPorts[secret] {\n\t\t\tanns[fmt.Sprintf(\"%s.%d\", certPath, p)] = volPath\n\t\t}\n\t}\n\treturn mounts, nil\n}\n\n// annotationPrefixedPorts will return a map of secret names to a slice of ports extracted from annotations that match the given prefix.\nfunc annotationPrefixedPorts(anns map[string]string, prefix string) (m map[string][]uint16, err error) {\n\tif _, ok := anns[prefix]; ok {\n\t\treturn nil, fmt.Errorf(`annotation %q must have a \".<port>\" suffix`, prefix)\n\t}\n\tprefix += \".\"\n\tfor k, v := range anns {\n\t\tif !strings.HasPrefix(k, prefix) {\n\t\t\tcontinue\n\t\t}\n\t\tps := k[len(prefix):]\n\t\tif len(ps) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"empty port suffix for annotation %s\", k)\n\t\t}\n\t\tpn, err := strconv.ParseUint(ps, 10, 16)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid port number %s for annotation %s\", ps, k)\n\t\t}\n\t\tif m == nil {\n\t\t\tm = make(map[string][]uint16)\n\t\t}\n\t\tm[v] = append(m[v], uint16(pn))\n\t}\n\treturn m, nil\n}\n\n// Find the security context of the first container (with both intercepts and a set security context) and ensure\n// that any env interpolations in it are prefixed with the env-prefix of the corresponding config container.\nfunc (a *ContainerBuilder) firstAppSecurityContext() (*core.SecurityContext, error) {\n\tcns := a.Pod.Spec.Containers\n\tfor _, cc := range a.Config.Containers {\n\t\tif len(cc.Intercepts) > 0 {\n\t\t\tfor i := range cns {\n\t\t\t\tapp := &cns[i]\n\t\t\t\tif app.Name != cc.Name {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif app.SecurityContext == nil {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tjs, err := json.Marshal(app.SecurityContext)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tsc := core.SecurityContext{}\n\t\t\t\terr = json.Unmarshal([]byte(prefixInterpolated(string(js), EnvPrefixApp+cc.EnvPrefix)), &sc)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn &sc, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, nil\n}\n\n// configuredContainers will find each container in the given config and match it against a container\n// in the pod using its name. The returned slice is guaranteed to use the same index as the Sidecar.Containers slice.\nfunc (a *ContainerBuilder) configuredContainers(ctx context.Context) []*core.Container {\n\tcns := a.Pod.Spec.Containers\n\tresult := make([]*core.Container, len(a.Config.Containers))\n\tfor ci, cc := range a.Config.Containers {\n\t\tfor i := range cns {\n\t\t\tapp := &cns[i]\n\t\t\tif app.Name == ContainerName {\n\t\t\t\t// The pod might hold JSON of replaced containers from an earlier patch\n\t\t\t\tannName := annotation.ReplacedContainerPrefix + cc.Name\n\t\t\t\tif appJson, ok := a.Pod.Annotations[annName]; ok {\n\t\t\t\t\tvar cn core.Container\n\t\t\t\t\terr := json.Unmarshal([]byte(appJson), &cn)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tclog.Errorf(ctx, \"failed to unmarshal container annotation %s: %v\", annName, err)\n\t\t\t\t\t}\n\t\t\t\t\tresult[ci] = &cn\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else if app.Name == cc.Name {\n\t\t\t\tresult[ci] = app\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn result\n}\n\nfunc (a *ContainerBuilder) eachConfiguredContainer(configureContainers []*core.Container, f func(*core.Container, *Container)) {\n\tfor i, cn := range configureContainers {\n\t\tif cn != nil {\n\t\t\tf(cn, a.Config.Containers[i])\n\t\t}\n\t}\n}\n\n// prefixInterpolated will prefix all environment variable names that are referenced using $(NAME) expressions\n// in the given string with the given prefix and return the result. Escaped expressions in the form $$(NAME),\n// unbalanced, or otherwise invalid expressions are not prefixed.\nfunc prefixInterpolated(str, pfx string) string {\n\tconst (\n\t\tstNormal = iota\n\t\tstDollarSeen\n\t\tstDollarParenSeen\n\t)\n\tst := stNormal\n\tvar bd, ev strings.Builder\n\tfor _, c := range str {\n\t\tswitch c {\n\t\tcase '$':\n\t\t\tswitch st {\n\t\t\tcase stDollarParenSeen:\n\t\t\t\t// '$' is not a legal character in an environment interpolation expression so\n\t\t\t\t// terminate that expression without prefixing it.\n\t\t\t\tbd.WriteString(ev.String())\n\t\t\t\tev.Reset()\n\t\t\t\tst = stDollarSeen\n\t\t\tcase stDollarSeen:\n\t\t\t\tst = stNormal\n\t\t\tdefault:\n\t\t\t\tst = stDollarSeen\n\t\t\t}\n\t\t\tbd.WriteByte('$')\n\t\tcase '(':\n\t\t\tswitch st {\n\t\t\tcase stDollarParenSeen:\n\t\t\t\t// '(' is not a legal character in an environment interpolation expression so\n\t\t\t\t// terminate that expression without prefixing it.\n\t\t\t\tbd.WriteString(ev.String())\n\t\t\t\tev.Reset()\n\t\t\t\tst = stNormal\n\t\t\tcase stDollarSeen:\n\t\t\t\tst = stDollarParenSeen\n\t\t\tdefault:\n\t\t\t\tst = stNormal\n\t\t\t}\n\t\t\tbd.WriteByte('(')\n\t\tcase ')':\n\t\t\tif st == stDollarParenSeen && ev.Len() > 0 {\n\t\t\t\tbd.WriteString(pfx)\n\t\t\t\tbd.WriteString(ev.String())\n\t\t\t\tev.Reset()\n\t\t\t}\n\t\t\tst = stNormal\n\t\t\tbd.WriteByte(')')\n\t\tdefault:\n\t\t\tswitch st {\n\t\t\tcase stDollarParenSeen:\n\t\t\t\tev.WriteRune(c)\n\t\t\tdefault:\n\t\t\t\tbd.WriteRune(c)\n\t\t\t\tst = stNormal\n\t\t\t}\n\t\t}\n\t}\n\tif ev.Len() > 0 {\n\t\t// Unbalanced interpolation. Just leave it as is.\n\t\tbd.WriteString(ev.String())\n\t}\n\treturn bd.String()\n}\n\nvar envRxReplace = regexp.MustCompile(`\\$\\(([^)]+)\\)`)\n\nfunc appendAppContainerEnv(app *core.Container, cc *Container, es []core.EnvVar) []core.EnvVar {\n\tpfx := EnvPrefixApp + cc.EnvPrefix\n\tpfxReplace := \"$(\" + pfx + \"$1)\"\n\tfor _, e := range app.Env {\n\t\te.Name = pfx + e.Name\n\t\te.Value = envRxReplace.ReplaceAllString(e.Value, pfxReplace)\n\t\tes = append(es, e)\n\t}\n\treturn es\n}\n\nfunc appendAppContainerEnvFrom(app *core.Container, cc *Container, es []core.EnvFromSource) []core.EnvFromSource {\n\tfor _, e := range app.EnvFrom {\n\t\te.Prefix = EnvPrefixApp + cc.EnvPrefix + e.Prefix\n\t\tes = append(es, e)\n\t}\n\treturn es\n}\n"
  },
  {
    "path": "pkg/agentconfig/container_test.go",
    "content": "package agentconfig\n\nimport (\n\t\"testing\"\n)\n\nfunc Test_prefixInterpolated(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targ  string\n\t\twant string\n\t}{\n\t\t{\n\t\t\t\"empty\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"empty_ipl\",\n\t\t\t\"$()\",\n\t\t\t\"$()\",\n\t\t},\n\t\t{\n\t\t\t\"alone\",\n\t\t\t\"$(IPL)\",\n\t\t\t\"$(_TEL_APP_A_IPL)\",\n\t\t},\n\t\t{\n\t\t\t\"normal\",\n\t\t\t\"Normal $(IPL) text\",\n\t\t\t\"Normal $(_TEL_APP_A_IPL) text\",\n\t\t},\n\t\t{\n\t\t\t\"escaped_ipl\",\n\t\t\t\"Escaped $$(IPL) text\",\n\t\t\t\"Escaped $$(IPL) text\",\n\t\t},\n\t\t{\n\t\t\t\"nested_ipl\",\n\t\t\t\"Nested $(IP$(IPL)) text\",\n\t\t\t\"Nested $(IP$(_TEL_APP_A_IPL)) text\",\n\t\t},\n\t\t{\n\t\t\t\"invalid_env\",\n\t\t\t\"Nested $(IP$) text\",\n\t\t\t\"Nested $(IP$) text\",\n\t\t},\n\t\t{\n\t\t\t\"unbalanced\",\n\t\t\t\"Unbalanced $(IPL text\",\n\t\t\t\"Unbalanced $(IPL text\",\n\t\t},\n\t\t{\n\t\t\t\"adjacent\",\n\t\t\t\"Adjacent $(IP1)$(IP2) text\",\n\t\t\t\"Adjacent $(_TEL_APP_A_IP1)$(_TEL_APP_A_IP2) text\",\n\t\t},\n\t\t{\n\t\t\t\"dollar-separated\",\n\t\t\t\"Dollar $(IP1)$$$(IP2) separated\",\n\t\t\t\"Dollar $(_TEL_APP_A_IP1)$$$(_TEL_APP_A_IP2) separated\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := prefixInterpolated(tt.arg, \"_TEL_APP_A_\"); got != tt.want {\n\t\t\t\tt.Errorf(\"prefixInterpolated(%q) = %q, want %q\", tt.arg, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/agentconfig/initcontainer.go",
    "content": "package agentconfig\n\nimport (\n\t\"fmt\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n)\n\nfunc InitContainer(config *Sidecar) *core.Container {\n\tic := &core.Container{\n\t\tName:  InitContainerName,\n\t\tImage: config.AgentImage,\n\t\tArgs:  []string{\"agent-init\"},\n\t\tEnv: []core.EnvVar{\n\t\t\t{\n\t\t\t\tName:  \"LOG_LEVEL\",\n\t\t\t\tValue: config.LogLevel.String(),\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"AGENT_CONFIG\",\n\t\t\t\tValueFrom: &core.EnvVarSource{\n\t\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tFieldPath:  fmt.Sprintf(\"metadata.annotations['%s']\", annotation.Config),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tName: \"POD_IP\",\n\t\t\t\tValueFrom: &core.EnvVarSource{\n\t\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tFieldPath:  \"status.podIP\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tSecurityContext: &core.SecurityContext{\n\t\t\tCapabilities: &core.Capabilities{\n\t\t\t\tAdd: []core.Capability{\"NET_ADMIN\"},\n\t\t\t},\n\t\t},\n\t}\n\tif r := config.InitResources; r != nil {\n\t\tic.Resources = *r\n\t}\n\tif s := config.InitSecurityContext; s != nil {\n\t\tic.SecurityContext = s\n\t}\n\treturn ic\n}\n"
  },
  {
    "path": "pkg/agentconfig/injectpolicy.go",
    "content": "package agentconfig\n\nimport (\n\t\"fmt\"\n)\n\n// InjectPolicy specifies when the agent injector mutating webhook will inject a traffic-agent into\n// a pod.\ntype InjectPolicy int\n\nvar epNames = [...]string{\"OnDemand\", \"WhenEnabled\", \"Never\"} //nolint:gochecknoglobals // constant names\n\nconst (\n\t// OnDemand tells the injector to inject the traffic-agent the first time someone makes an attempt\n\t// to intercept the workload, even if the telepresence.io/inject-traffic-agent is\n\t// missing.\n\t//\n\t// OnDemand has lower priority than the annotation. If the annotation is set to \"enabled\", then\n\t// the injector will inject the traffic-agent in advance into all pods that are created or updated.\n\t// If it is \"disabled\", then no injection will take place.\n\t//\n\t// This is the default setting.\n\tOnDemand InjectPolicy = iota\n\n\t// WhenEnabled tells the injector to inject the traffic-agent in advance into all pods that are\n\t// created or updated when the telepresence.io/inject-traffic-agent annotation is\n\t// present and set to \"enabled\".\n\tWhenEnabled\n\n\t// Never will disable the injector.\n\tNever\n)\n\nfunc (aps InjectPolicy) String() string {\n\treturn epNames[aps]\n}\n\nfunc NewEnablePolicy(s string) (InjectPolicy, error) {\n\tfor i, n := range epNames {\n\t\tif s == n {\n\t\t\treturn InjectPolicy(i), nil\n\t\t}\n\t}\n\treturn 0, fmt.Errorf(\"invalid InjectPolicy: %q\", s)\n}\n\nfunc (aps InjectPolicy) MarshalJSON() ([]byte, error) {\n\treturn []byte(aps.String()), nil\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (aps *InjectPolicy) EnvDecode(val string) (err error) {\n\tvar as InjectPolicy\n\tif val == \"\" {\n\t\tas = OnDemand\n\t} else if as, err = NewEnablePolicy(val); err != nil {\n\t\treturn err\n\t}\n\t*aps = as\n\treturn nil\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (aps *InjectPolicy) UnmarshalText(value []byte) error {\n\treturn aps.EnvDecode(string(value))\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (aps *InjectPolicy) UnmarshalJSON(value []byte) error {\n\treturn aps.EnvDecode(string(value))\n}\n"
  },
  {
    "path": "pkg/agentconfig/intercepttarget.go",
    "content": "package agentconfig\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// InterceptTarget describes the mapping between service ports and one container port, or if no service\n// is used, just the container port.\n// All entries must be guaranteed to all have the same Protocol, ContainerPort, and AgentPort.\n// The slice must be considered immutable once created using NewInterceptTarget.\ntype InterceptTarget []*Intercept\n\nfunc NewInterceptTarget(ics []*Intercept) InterceptTarget {\n\t// This is a parameter assertion. If it is triggered, then something is dead wrong in the caller code.\n\tni := len(ics)\n\tif ni == 0 {\n\t\tpanic(\"attempt to add intercept create an InterceptTarget with no Intercepts\")\n\t}\n\tif ni > 1 {\n\t\ticZero := ics[0]\n\t\tfor i := 1; i < ni; i++ {\n\t\t\tic := ics[i]\n\t\t\tif icZero.AgentPort != ic.AgentPort || icZero.ContainerPort != ic.ContainerPort || icZero.Protocol != ic.Protocol {\n\t\t\t\tpanic(\"attempt to add intercept to an InterceptTarget with different AgentPort or ContainerPort\")\n\t\t\t}\n\t\t}\n\t}\n\treturn ics\n}\n\nfunc (cp InterceptTarget) MatchForSpec(spec *manager.InterceptSpec) bool {\n\tic := cp[0]\n\tcnPort := uint16(spec.ContainerPort)\n\treturn cnPort == ic.ContainerPort && ic.Protocol == types.FromK8sProtocol(core.Protocol(spec.Protocol))\n}\n\nfunc (cp InterceptTarget) AgentPort() uint16 {\n\treturn cp[0].AgentPort\n}\n\nfunc (cp InterceptTarget) TargetPortNumeric() bool {\n\tfor _, ic := range cp {\n\t\tif ic.TargetPortNumeric {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (cp InterceptTarget) ContainerPort() uint16 {\n\treturn cp[0].ContainerPort\n}\n\nfunc (cp InterceptTarget) ContainerPortName() string {\n\treturn cp[0].ContainerPortName\n}\n\nfunc (cp InterceptTarget) Protocol() types.Proto {\n\treturn cp[0].Protocol\n}\n\nfunc portString(ic *Intercept) (s string) {\n\tif ic.ServiceUID != \"\" {\n\t\tp := ic.ServicePortName\n\t\tif p == \"\" {\n\t\t\tp = strconv.Itoa(int(ic.ServicePort))\n\t\t}\n\t\treturn fmt.Sprintf(\"service port %s:%s\", ic.ServiceName, p)\n\t}\n\tp := ic.ContainerPortName\n\tif p == \"\" {\n\t\tp = strconv.Itoa(int(ic.ContainerPort))\n\t}\n\treturn fmt.Sprintf(\"container port %s\", p)\n}\n\nfunc (cp InterceptTarget) AppProtocol(ctx context.Context) (proto string) {\n\tvar foundIc *Intercept\n\tfor _, ic := range cp {\n\t\tif ic.AppProtocol == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif foundIc == nil {\n\t\t\tfoundIc = ic\n\t\t\tproto = foundIc.AppProtocol\n\t\t} else if foundIc.AppProtocol != ic.AppProtocol {\n\t\t\tclog.Warnf(ctx, \"%s appProtocol %s differs from %s appProtocol %s. %s will be used for %s\",\n\t\t\t\tportString(foundIc), proto,\n\t\t\t\tportString(ic), ic.AppProtocol,\n\t\t\t\tproto, portString(ic))\n\t\t}\n\t}\n\treturn proto\n}\n\nfunc (cp InterceptTarget) HasServicePortName(name string) bool {\n\tfor _, sv := range cp {\n\t\tif sv.ServicePortName == name {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (cp InterceptTarget) HasServicePort(port uint16) bool {\n\tfor _, sv := range cp {\n\t\tif sv.ServicePort == port {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (cp InterceptTarget) String() string {\n\tsb := bytes.Buffer{}\n\tl := len(cp)\n\tif l > 1 {\n\t\tsb.WriteByte('[')\n\t}\n\tfor i, ic := range cp {\n\t\tif i > 0 {\n\t\t\tswitch l {\n\t\t\tcase 2:\n\t\t\t\tsb.WriteString(\" and \")\n\t\t\tcase i + 1:\n\t\t\t\tsb.WriteString(\", and \")\n\t\t\tdefault:\n\t\t\t\tsb.WriteString(\", \")\n\t\t\t}\n\t\t}\n\t\tsb.WriteString(portString(ic))\n\t}\n\tif l > 1 {\n\t\tsb.WriteByte(']')\n\t}\n\tif l > 1 || cp[0].ServiceName != \"\" {\n\t\tioutil.Printf(&sb, \" => container port %d/%s\", cp.ContainerPort(), cp.Protocol())\n\t}\n\treturn sb.String()\n}\n"
  },
  {
    "path": "pkg/agentconfig/sidecar.go",
    "content": "package agentconfig\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\tk8sTypes \"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nconst (\n\tContainerName           = \"traffic-agent\"\n\tManagerAppName          = \"traffic-manager\"\n\tInitContainerName       = \"tel-agent-init\"\n\tMountPrefixApp          = \"/tel_app_mounts\"\n\tExportsVolumeName       = \"export-volume\"\n\tExportsMountPoint       = \"/tel_app_exports\"\n\tTempVolumeName          = \"tel-agent-tmp\"\n\tTempMountPoint          = \"/tmp\"\n\tEnvPrefix               = \"_TEL_\"\n\tEnvPrefixAgent          = EnvPrefix + \"AGENT_\"\n\tEnvPrefixApp            = EnvPrefix + \"APP_\"\n\tPodInfoVolumeName       = \"pod-info\"\n\tPodInfoMountPath        = \"/etc/podinfo\"\n\tDownstreamTLSVolumeName = \"downstream-tls\"\n\tDownstreamTLSVolumePath = \"/downstream-tls\"\n\tUpstreamTLSVolumeName   = \"upstream-tls\"\n\tUpstreamTLSVolumePath   = \"/upstream-tls\"\n\n\t// EnvAgentConfig is the environment variable where the traffic-agent finds its own config.\n\tEnvAgentConfig = \"AGENT_CONFIG\"\n\n\t// EnvInterceptContainer intercepted container propagated to client during intercept.\n\tEnvInterceptContainer = \"TELEPRESENCE_CONTAINER\"\n\n\t// EnvInterceptMounts mount points propagated to client during intercept.\n\tEnvInterceptMounts = \"TELEPRESENCE_MOUNTS\"\n\n\t// EnvLocalMounts mount points that the client should mount locally (e.g. /tmp).\n\tEnvLocalMounts = \"TELEPRESENCE_LOCAL_MOUNTS\"\n\n\t// EnvAPIHost is the host name of the Telepresence API server when it is enabled.\n\tEnvAPIHost = \"TELEPRESENCE_API_HOST\"\n\n\t// EnvAPIPort is the port number of the Telepresence API server when it is enabled.\n\tEnvAPIPort = \"TELEPRESENCE_API_PORT\"\n\n\tWorkloadNameLabel    = annotation.DomainPrefix + \"workloadName\"\n\tWorkloadKindLabel    = annotation.DomainPrefix + \"workloadKind\"\n\tWorkloadEnabledLabel = annotation.DomainPrefix + \"workloadEnabled\"\n)\n\ntype ReplacePolicy int\n\nconst (\n\t// ReplacePolicyIntercept The traffic-agent will receive all traffic intended for the ports of the app-container and\n\t// then either route that traffic to the client or to the original app-container depending on if the port is\n\t// intercepted or not. This will require an init-container when the targetPort of the service is numeric or\n\t// when the service is headless.\n\tReplacePolicyIntercept ReplacePolicy = iota\n\n\t// ReplacePolicyContainer The traffic-agent is currently replacing the app container and routes all traffic to the\n\t// client.\n\tReplacePolicyContainer\n\n\t// ReplacePolicyInactive The traffic-agent is not interfering with any ports or containers.\n\tReplacePolicyInactive\n)\n\n// Intercept describes the mapping between a service port and an intercepted container port or, when\n// service is used, just the container port.\ntype Intercept struct {\n\t// The name of the intercepted container port\n\tContainerPortName string `json:\"containerPortName,omitzero\"`\n\n\t// Name of intercepted service\n\tServiceName string `json:\"serviceName,omitzero\"`\n\n\t// UID of intercepted service\n\tServiceUID k8sTypes.UID `json:\"serviceUID,omitzero\"`\n\n\t// Name of intercepted service port\n\tServicePortName string `json:\"servicePortName,omitzero\"`\n\n\t// TargetPortNumeric is set to true unless the servicePort has a symbolic target port\n\tTargetPortNumeric bool `json:\"targetPortNumeric,omitzero\"`\n\n\t// L4 protocol used by the intercepted port\n\tProtocol types.Proto `json:\"protocol,omitzero\"`\n\n\t// L7 protocol used by the intercepted port\n\tAppProtocol string `json:\"appProtocol,omitzero\"`\n\n\t// True if the service is headless\n\tHeadless bool `json:\"headless,omitzero\"`\n\n\t// The number of the intercepted container port\n\tContainerPort uint16 `json:\"containerPort,omitzero\"`\n\n\t// Number of intercepted service port\n\tServicePort uint16 `json:\"servicePort,omitzero\"`\n\n\t// The port number that the agent listens to\n\tAgentPort uint16 `json:\"agentPort,omitzero\"`\n}\n\n// Container describes one container that can have one or several intercepts.\ntype Container struct {\n\t// Name of the intercepted container\n\tName string `json:\"name,omitempty\" yaml:\"name,omitzero\"`\n\n\t// The intercepts managed by the agent\n\tIntercepts []*Intercept `json:\"intercepts,omitempty\"`\n\n\t// Prefix used for all keys in the container environment copy\n\tEnvPrefix string `json:\"envPrefix,omitzero\"`\n\n\t// Where the agent mounts its volumes\n\tMountPoint string `json:\"mountPoint,omitzero\"`\n\n\t// Mounts controls how the traffic-agent makes mounts available for this container. Each\n\t// policy is keyed with either the name of a volume or by a path prefix that matches the mounted\n\t// path.\n\tMounts types.MountPolicies `json:\"mounts,omitempty\"`\n\n\t// MountPaths are the actual mount points that are mounted by this container\n\t//\n\t// Deprecated: Use Mounts.\n\tMountPaths []string `json:\"Mounts,omitempty\"`\n\n\t// Replace is whether the agent should replace the intercepted container, it's ports, or nothing.\n\tReplace ReplacePolicy `json:\"replace,omitzero\"`\n}\n\n// The Sidecar configures the traffic-agent sidecar.\ntype Sidecar struct {\n\t// If Create is true, then this Config has not yet been filled in.\n\tCreate bool `json:\"create,omitzero\"`\n\n\t// If Manual is true, then this Config is created manually.\n\tManual bool `json:\"manual,omitzero\"`\n\n\t// The fully qualified name of the traffic-agent image, i.e. \"ghcr.io/telepresenceio/tel2:2.5.4\".\n\tAgentImage string `json:\"agentImage,omitzero\"`\n\n\t// One of \"IfNotPresent\", \"Always\", or \"Never\".\n\tPullPolicy string `json:\"pullPolicy,omitzero\"`\n\n\t// Secrets used when pulling the agent image from a private registry.\n\tPullSecrets []core.LocalObjectReference `json:\"pullSecrets,omitempty\"`\n\n\t// The name of the traffic-agent instance. Typically, the same as the name of the workload owner.\n\tAgentName string `json:\"agentName,omitzero\"`\n\n\t// The namespace of the intercepted pod.\n\tNamespace string `json:\"namespace,omitzero\"`\n\n\t// LogLevel used for all traffic-agent logging.\n\tLogLevel slog.Level `json:\"logLevel,omitzero\"`\n\n\t// The name of the workload that the pod originates from.\n\tWorkloadName string `json:\"workloadName,omitzero\"`\n\n\t// The kind of workload that the pod originates from.\n\tWorkloadKind k8sapi.Kind `json:\"workloadKind,omitzero\"`\n\n\t// The host used when connecting to the traffic-manager.\n\tManagerHost string `json:\"managerHost,omitzero\"`\n\n\t// The port used when connecting to the traffic manager.\n\tManagerPort uint16 `json:\"managerPort,omitzero\"`\n\n\t// The port used by the agents restFUL API server.\n\tAPIPort uint16 `json:\"apiPort,omitzero\"`\n\n\t// Resources for the sidecar.\n\tResources *core.ResourceRequirements `json:\"resources,omitempty\"`\n\n\t// InitResources is the resource requirements for the initContainer sidecar.\n\tInitResources *core.ResourceRequirements `json:\"initResources,omitempty\"`\n\n\t// MountPolicies controls how the agent will handle new mounts that might arrive when\n\t// the pod is created.\n\tMountPolicies types.MountPolicies `json:\"mountPolicies,omitzero\"`\n\n\t// The intercepts managed by the agent.\n\tContainers []*Container `json:\"containers,omitempty\"`\n\n\t// SecurityContext for the sidecar.\n\tSecurityContext *core.SecurityContext `json:\"securityContext,omitempty\"`\n\n\t// InitSecurityContext is the SecurityContext for the initContainer sidecar.\n\tInitSecurityContext *core.SecurityContext `json:\"initSecurityContext,omitempty\"`\n\n\t// ClientConnectionTTL is the maximum duration that the traffic-agent will keep an idle client connection alive.\n\tClientConnectionTTL time.Duration `json:\"clientConnectionTTL,omitempty,format:units\"`\n\n\t// EnableMetrics is true if the traffic-agent should send consumption reports to the traffic-manager.\n\tEnableMetrics bool `json:\"enableMetrics,omitempty\"`\n\n\t// EnableH2cProbing is true if the traffic-agent should enable H2C probing on TCP ports that have no TLS and no appProtocol.\n\tEnableH2cProbing bool `json:\"enableH2cProbing,omitempty\"`\n\n\t// WatchRetryInterval is the interval between retries that a watcher uses when the gRPC connection to the traffic-manager is lost.\n\tWatchRetryInterval time.Duration `json:\"watchRetryInterval,format:units\"`\n}\n\n// InterceptTarget returns the container and intercepts that are parents of the given container port and protocol.\nfunc (s *Sidecar) InterceptTarget(containerPort uint16, proto types.Proto) (*Container, InterceptTarget) {\n\tfor _, c := range s.Containers {\n\t\tfor i, ic := range c.Intercepts {\n\t\t\tif ic.ContainerPort == containerPort && ic.Protocol == proto {\n\t\t\t\tit := InterceptTarget{ic}\n\t\t\t\ti++\n\t\t\t\tif i < len(c.Intercepts) {\n\t\t\t\t\tfor _, ic := range c.Intercepts[i:] {\n\t\t\t\t\t\tif ic.ContainerPort == containerPort && ic.Protocol == proto {\n\t\t\t\t\t\t\tit = append(it, ic)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn c, it\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, nil\n}\n\n// InterceptorInactivePort returns the port that the interceptor should write to when it isn't serving\n// an intercept. The port will be the container port unless some service uses a numeric target port\n// that targets the container port.\n//\n// When a numeric target port is specified, the init-container sets up an iptables NAT PREROUTING rule\n// to redirect all traffic destined for the container port to the corresponding port where the agent's\n// forwarder is listening. When no intercept is active, the forwarder routes traffic to the container\n// port using the pod's IP address. However, directly routing to the container port would trigger the\n// NAT PREROUTING rule again, causing an infinite loop. To avoid this, the forwarder uses a proxy port,\n// which is redirected to the container port via an iptables NAT OUTPUT rule.\nfunc (s *Sidecar) InterceptorInactivePort(containerPort uint16, proto types.Proto) uint16 {\n\t_, it := s.InterceptTarget(containerPort, proto)\n\tif it != nil && it.TargetPortNumeric() {\n\t\treturn s.ProxyPort(it.AgentPort())\n\t}\n\treturn containerPort\n}\n\n// Clone returns a deep copy of the Sidecar.\nfunc (s *Sidecar) Clone() *Sidecar {\n\tcs := *s\n\tfor ci, cn := range cs.Containers {\n\t\tccn := *cn\n\t\tcs.Containers[ci] = &ccn\n\t\tfor ii, ic := range ccn.Intercepts {\n\t\t\tcic := *ic\n\t\t\tccn.Intercepts[ii] = &cic\n\t\t}\n\t}\n\treturn &cs\n}\n\n// EachContainer will find each container and match it against a container\n// in the pod using its name. The given function is called once for each match.\nfunc (s *Sidecar) EachContainer(pod *core.Pod, f func(*core.Container, *Container)) {\n\tcns := pod.Spec.Containers\n\tfor _, cc := range s.Containers {\n\t\tfor i := range cns {\n\t\t\tif app := &cns[i]; app.Name == cc.Name {\n\t\t\t\tf(app, cc)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n// FindIntercept finds the [Container] and [Intercept] configuration that matches the given service name, container name, and service- or container port.\n// The port will be considered a service port for intercepts that have a service UID and a container port for service less intercepts.\nfunc (s *Sidecar) FindIntercept(serviceName, containerName string, port types.PortIdentifier) (foundCN *Container, foundIC *Intercept, err error) {\n\tfor _, cn := range s.Containers {\n\t\tfor _, ic := range cn.Intercepts {\n\t\t\tif !(serviceName == \"\" || serviceName == ic.ServiceName) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif port != \"\" {\n\t\t\t\tif ic.ServiceUID != \"\" {\n\t\t\t\t\tif !IsInterceptForService(port, ic) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t} else if !IsInterceptForContainer(port, ic) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\t\t\tif foundIC == nil {\n\t\t\t\tfoundCN = cn\n\t\t\t\tif containerName != \"\" {\n\t\t\t\t\tfor _, cx := range s.Containers {\n\t\t\t\t\t\tif cx.Name == containerName {\n\t\t\t\t\t\t\tfoundCN = cx\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfoundIC = ic\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar msg string\n\t\t\tswitch {\n\t\t\tcase serviceName == \"\" && port == \"\":\n\t\t\t\tmsg = fmt.Sprintf(\"%s %s.%s has multiple interceptable ports.\\n\"+\n\t\t\t\t\t\"Please specify the service and/or port you want to intercept \"+\n\t\t\t\t\t\"by passing the --service=<svc> and/or --port=<local:portName/portNumber> flag.\",\n\t\t\t\t\ts.WorkloadKind, s.WorkloadName, s.Namespace)\n\t\t\tcase serviceName == \"\":\n\t\t\t\tmsg = fmt.Sprintf(\"%s %s.%s has multiple interceptable services with port %s.\\n\"+\n\t\t\t\t\t\"Please specify the service you want to intercept by passing the --service=<svc> flag.\",\n\t\t\t\t\ts.WorkloadKind, s.WorkloadName, s.Namespace, port)\n\t\t\tcase port == \"\":\n\t\t\t\tmsg = fmt.Sprintf(\"%s %s.%s has multiple interceptable ports in service %s.\\n\"+\n\t\t\t\t\t\"Please specify the port you want to intercept by passing the --port=<local:svcPortName> flag.\",\n\t\t\t\t\ts.WorkloadKind, s.WorkloadName, s.Namespace, serviceName)\n\t\t\tdefault:\n\t\t\t\tmsg = fmt.Sprintf(\"%s %s.%s intercept config is broken. Service %s, port %s is declared more than once\\n\",\n\t\t\t\t\ts.WorkloadKind, s.WorkloadName, s.Namespace, serviceName, port)\n\t\t\t}\n\t\t\treturn nil, nil, errcat.User.New(msg)\n\t\t}\n\t}\n\tif foundIC != nil {\n\t\treturn foundCN, foundIC, nil\n\t}\n\n\tss := \"\"\n\tif serviceName != \"\" {\n\t\tif port != \"\" {\n\t\t\tss = fmt.Sprintf(\" matching service %s, port %s\", serviceName, port)\n\t\t} else {\n\t\t\tss = fmt.Sprintf(\" matching service %s\", serviceName)\n\t\t}\n\t} else if port != \"\" {\n\t\tss = fmt.Sprintf(\" matching port %s\", port)\n\t}\n\treturn nil, nil, errcat.User.Newf(\"%s %s.%s has no interceptable port%s\", s.WorkloadKind, s.WorkloadName, s.Namespace, ss)\n}\n\n// Marshal returns YAML encoding of the Sidecar.\nfunc (s *Sidecar) Marshal() ([]byte, error) {\n\treturn yaml.Marshal(s)\n}\n\n// UnmarshalYAML creates a new instance of the SidecarType from the given YAML data.\nfunc UnmarshalYAML(data []byte) (*Sidecar, error) {\n\tinto := new(Sidecar)\n\tdata, err := yaml.YAMLToJSON(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := json.Unmarshal(data, into, true); err != nil {\n\t\treturn nil, err\n\t}\n\treturn into, nil\n}\n\n// MarshalTight marshals the given instance into JSON data, with data relating to the creation of the\n// container manifest stripped off.\nfunc MarshalTight(ac *Sidecar) (string, error) {\n\t// Strip things that are not needed once the container has been created.\n\tai := ac.AgentImage\n\tpp := ac.PullPolicy\n\tps := ac.PullSecrets\n\tir := ac.InitResources\n\tsc := ac.SecurityContext\n\tis := ac.InitSecurityContext\n\n\tac.AgentImage = \"\"\n\tac.PullPolicy = \"\"\n\tac.PullSecrets = nil\n\tac.InitResources = nil\n\tac.SecurityContext = nil\n\tac.InitSecurityContext = nil\n\n\tdata, err := json.Marshal(ac)\n\tac.AgentImage = ai\n\tac.PullPolicy = pp\n\tac.PullSecrets = ps\n\tac.InitResources = ir\n\tac.SecurityContext = sc\n\tac.InitSecurityContext = is\n\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(data), err\n}\n\n// UnmarshalJSON creates a new instance of the SidecarType from the given JSON data.\nfunc UnmarshalJSON(data string) (*Sidecar, error) {\n\tinto := new(Sidecar)\n\tif err := json.Unmarshal([]byte(data), into, true); err != nil {\n\t\treturn nil, err\n\t}\n\treturn into, nil\n}\n"
  },
  {
    "path": "pkg/agentconfig/util.go",
    "content": "package agentconfig\n\nimport \"github.com/telepresenceio/telepresence/v2/pkg/types\"\n\n// IsInterceptForService returns true when the given PortIdentifier is equal to the\n// config's ServicePortName, or can be parsed to an integer equal to the config's ServicePort.\nfunc IsInterceptForService(pi types.PortIdentifier, ic *Intercept) bool {\n\tproto, name, num := pi.ProtoAndNameOrNumber()\n\tif pi.HasProto() && proto != ic.Protocol {\n\t\treturn false\n\t}\n\tif name == \"\" {\n\t\treturn num == ic.ServicePort\n\t}\n\treturn name == ic.ServicePortName\n}\n\n// IsInterceptForContainer returns true when the given PortIdentifier is equal to the\n// config's ContainerPort, or can be parsed to an integer equal to the config's ContainerPort.\nfunc IsInterceptForContainer(pi types.PortIdentifier, ic *Intercept) bool {\n\tproto, name, num := pi.ProtoAndNameOrNumber()\n\tif pi.HasProto() && proto != ic.Protocol {\n\t\treturn false\n\t}\n\tif name == \"\" {\n\t\treturn num == ic.ContainerPort\n\t}\n\treturn name == ic.ContainerPortName\n}\n\n// PortUniqueIntercepts returns a slice of intercepts for the container where each intercept\n// is unique with respect to the AgentPort and Protocol.\n// This method should always be used when iterating the intercepts, except for when an\n// intercept is identified via a service.\nfunc PortUniqueIntercepts(cn *Container) []*Intercept {\n\tum := make(map[types.PortAndProto]struct{}, len(cn.Intercepts))\n\tics := make([]*Intercept, 0, len(cn.Intercepts))\n\tfor _, ic := range cn.Intercepts {\n\t\tk := types.PortAndProto{Port: ic.AgentPort, Proto: ic.Protocol}\n\t\tif _, ok := um[k]; !ok {\n\t\t\tum[k] = struct{}{}\n\t\t\tics = append(ics, ic)\n\t\t}\n\t}\n\treturn ics\n}\n\n// ProxyPort returns a port that can be used as a proxy for a container port for the given\n// agentPort (the listener port for the traffic agent).\n// The proxy port will be the agentPort + the maximum number of possible intercepts for the sidecar.\nfunc (s *Sidecar) ProxyPort(agentPort uint16) uint16 {\n\treturn agentPort + 11 + uint16(s.numberOfPossibleIntercepts())\n}\n\nfunc (s *Sidecar) numberOfPossibleIntercepts() (count int) {\n\tfor _, c := range s.Containers {\n\t\tcount += len(c.Intercepts)\n\t}\n\treturn count\n}\n"
  },
  {
    "path": "pkg/agentconfig/volumes.go",
    "content": "package agentconfig\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nfunc AgentVolumes(agentName string, pod *core.Pod) (volumes []core.Volume, err error) {\n\tvolumes = []core.Volume{\n\t\t{\n\t\t\tName: PodInfoVolumeName,\n\t\t\tVolumeSource: core.VolumeSource{\n\t\t\t\tDownwardAPI: &core.DownwardAPIVolumeSource{\n\t\t\t\t\tItems: []core.DownwardAPIVolumeFile{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tPath: \"annotations\",\n\t\t\t\t\t\t\tFieldRef: &core.ObjectFieldSelector{\n\t\t\t\t\t\t\t\tFieldPath: \"metadata.annotations\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tDefaultMode: nil,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: ExportsVolumeName,\n\t\t\tVolumeSource: core.VolumeSource{\n\t\t\t\tEmptyDir: &core.EmptyDirVolumeSource{},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: TempVolumeName,\n\t\t\tVolumeSource: core.VolumeSource{\n\t\t\t\tEmptyDir: &core.EmptyDirVolumeSource{},\n\t\t\t},\n\t\t},\n\t}\n\n\tif pod == nil {\n\t\t// Called from genyaml so no pod is available to provide annotations.\n\t\treturn volumes, nil\n\t}\n\n\t// The name of the TLS secret in the annotations might contain environment variable expansions. The expansions\n\t// allowed here are \"$AGENT_NAME\" and \"$_TEL_AGENT_NAME\". The latter is for backward compatibility with older\n\t// agents where this expansion happened in the traffic-agent.\n\tenv := dos.MapEnv{\"AGENT_NAME\": agentName}\n\tvolumes, err = appendSecretVolume(env, annotation.DownstreamTLSSecret, pod, volumes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn appendSecretVolume(env, annotation.UpstreamTLSSecret, pod, volumes)\n}\n\nfunc appendSecretVolume(env dos.Env, annotation string, pod *core.Pod, volumes []core.Volume) ([]core.Volume, error) {\n\tsecretsAndPorts, err := annotationPrefixedPorts(pod.Annotations, annotation)\n\tif err != nil || len(secretsAndPorts) == 0 {\n\t\treturn volumes, err\n\t}\n\tfor _, secret := range maps.SortedKeys(secretsAndPorts) {\n\t\tvolumes = append(volumes, core.Volume{\n\t\t\tName: fmt.Sprintf(\"%s-vol\", secret),\n\t\t\tVolumeSource: core.VolumeSource{\n\t\t\t\tSecret: &core.SecretVolumeSource{\n\t\t\t\t\tSecretName: env.ExpandEnv(secret),\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\treturn volumes, nil\n}\n\nfunc (a *ContainerBuilder) appendVolumeMounts(app *core.Container, cc *Container, mounts []core.VolumeMount) []core.VolumeMount {\n\tpfx := EnvPrefixApp + cc.EnvPrefix\n\tfor _, m := range app.VolumeMounts {\n\t\tmp := a.Config.MountPolicies.Get(m.Name, m.MountPath)\n\t\tswitch mp {\n\t\tcase types.MountPolicyIgnore, types.MountPolicyLocal:\n\t\tcase types.MountPolicyRemoteReadOnly:\n\t\t\tif !m.ReadOnly {\n\t\t\t\trco := core.RecursiveReadOnlyIfPossible\n\t\t\t\tm.ReadOnly = true\n\t\t\t\tm.RecursiveReadOnly = &rco\n\t\t\t}\n\t\t\tfallthrough\n\t\tdefault:\n\t\t\tm.Name = prefixInterpolated(m.Name, pfx)\n\t\t\tm.MountPath = prefixInterpolated(cc.MountPoint+\"/\"+strings.TrimPrefix(m.MountPath, \"/\"), pfx)\n\t\t\tm.SubPath = prefixInterpolated(m.SubPath, pfx)\n\t\t\tm.SubPathExpr = prefixInterpolated(m.SubPathExpr, pfx)\n\t\t\tmounts = append(mounts, m)\n\t\t}\n\t}\n\treturn mounts\n}\n"
  },
  {
    "path": "pkg/agentmap/capsbase26.go",
    "content": "package agentmap\n\n// CapsBase26 converts the given number into base 26, represented using the letters 'A' to 'Z'.\nfunc CapsBase26(v uint64) string {\n\treturn addBase26('A', v)\n}\n\n// Base26 converts the given number into base 26, represented using the letters 'a' to 'z'.\nfunc Base26(v uint64) string {\n\treturn addBase26('a', v)\n}\n\n// Base26 converts the given number into base 26 represented using the letters 'a' to 'z'.\nfunc addBase26(c byte, v uint64) string {\n\ti := 14 // covers v == math.MaxUint64\n\tb := make([]byte, i)\n\tfor {\n\t\tl := v % 26\n\t\ti--\n\t\tb[i] = c + byte(l)\n\t\tif v < 26 {\n\t\t\tbreak\n\t\t}\n\t\tv /= 26\n\t}\n\treturn string(b[i:])\n}\n"
  },
  {
    "path": "pkg/agentmap/capsbase26_test.go",
    "content": "package agentmap\n\nimport (\n\t\"math\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestCapsBase26(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tv    uint64\n\t\twant string\n\t}{\n\t\t{\n\t\t\t\"zero\",\n\t\t\t0,\n\t\t\t\"A\",\n\t\t},\n\t\t{\n\t\t\t\"25\",\n\t\t\t25,\n\t\t\t\"Z\",\n\t\t},\n\t\t{\n\t\t\t\"26\",\n\t\t\t26,\n\t\t\t\"BA\",\n\t\t},\n\t\t{\n\t\t\t\"51\",\n\t\t\t26 + 25,\n\t\t\t\"BZ\",\n\t\t},\n\t\t{\n\t\t\t\"52\",\n\t\t\t2 * 26,\n\t\t\t\"CA\",\n\t\t},\n\t\t{\n\t\t\t\"1351\",\n\t\t\t2*26*26 - 1,\n\t\t\t\"BZZ\",\n\t\t},\n\t\t{\n\t\t\t\"1352\",\n\t\t\t2 * 26 * 26,\n\t\t\t\"CAA\",\n\t\t},\n\t\t{\n\t\t\t\"maxuint\",\n\t\t\tmath.MaxUint64,\n\t\t\t\"HLHXCZMXSYUMQP\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, tt.want, CapsBase26(tt.v), \"CapsBase26(%v)\", tt.v)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/agentmap/discorvery.go",
    "content": "package agentmap\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"sort\"\n\n\tcore \"k8s.io/api/core/v1\"\n\tk8sErrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\tapps \"k8s.io/client-go/informers/apps/v1\"\n\n\targorollouts \"github.com/datawire/argo-rollouts-go-client/pkg/client/informers/externalversions/rollouts/v1alpha1\"\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/informer\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\nvar ReplicaSetNameRx = regexp.MustCompile(`\\A(.+)-[a-f0-9]+\\z`)\n\ntype WorkloadOwnerNotFoundError struct {\n\t*k8sErrors.StatusError\n}\n\nfunc FindOwnerWorkload(ctx context.Context, obj k8sapi.Object, supportedWorkloadKinds k8sapi.Kinds) (k8sapi.Workload, error) {\n\tclog.Tracef(ctx, \"FindOwnerWorkload(%s,%s,%s)\", obj.GetName(), obj.GetNamespace(), obj.GetKind())\n\tlbs := obj.GetLabels()\n\tif wlName, ok := lbs[agentconfig.WorkloadNameLabel]; ok {\n\t\tkind, ok := lbs[agentconfig.WorkloadKindLabel]\n\t\tif ok && !supportedWorkloadKinds.Contains(k8sapi.Kind(kind)) {\n\t\t\treturn nil, fmt.Errorf(\"unable to find %s owner for %s.%s (annotation controlled)\",\n\t\t\t\tkind, obj.GetName(), obj.GetNamespace())\n\t\t}\n\t\treturn GetWorkload(ctx, wlName, obj.GetNamespace(), k8sapi.Kind(kind))\n\t}\n\trefs := obj.GetOwnerReferences()\n\tns := obj.GetNamespace()\n\tfor i := range refs {\n\t\tif or := &refs[i]; or.Controller != nil && *or.Controller {\n\t\t\tkind := k8sapi.Kind(or.Kind)\n\t\t\tif kind == k8sapi.ReplicaSetKind && supportedWorkloadKinds.Contains(k8sapi.DeploymentKind) {\n\t\t\t\t// Try the common case first. Strip replicaset's generated hash and try to\n\t\t\t\t// get the deployment. If this succeeds, we have saved us a replicaset\n\t\t\t\t// lookup.\n\t\t\t\tif m := ReplicaSetNameRx.FindStringSubmatch(or.Name); m != nil {\n\t\t\t\t\tif wl, err := GetWorkload(ctx, m[1], ns, k8sapi.DeploymentKind); err == nil {\n\t\t\t\t\t\treturn wl, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif supportedWorkloadKinds.Contains(kind) {\n\t\t\t\twl, err := GetWorkload(ctx, or.Name, ns, kind)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn FindOwnerWorkload(ctx, wl, supportedWorkloadKinds)\n\t\t\t}\n\t\t\t// A controller owner of unsupported workload kind is treated as \"no owner\".\n\t\t\tbreak\n\t\t}\n\t}\n\tif wl, ok := obj.(k8sapi.Workload); ok {\n\t\treturn wl, nil\n\t}\n\treturn nil, &WorkloadOwnerNotFoundError{StatusError: k8sErrors.NewNotFound(\n\t\tobj.GetGroupResource(), fmt.Sprintf(\"%s.%s\", obj.GetName(), obj.GetNamespace()))}\n}\n\nfunc GetWorkload(ctx context.Context, name, namespace string, workloadKind k8sapi.Kind) (obj k8sapi.Workload, err error) {\n\tclog.Tracef(ctx, \"GetWorkload(%s,%s,%s)\", name, namespace, workloadKind)\n\ti := informer.GetFactory(ctx, namespace)\n\tif i == nil {\n\t\tclog.Debugf(ctx, \"fetching %s %s.%s using direct API call\", workloadKind, name, namespace)\n\t\treturn k8sapi.GetWorkload(ctx, name, namespace, workloadKind)\n\t}\n\tai, ri := i.GetK8sInformerFactory().Apps().V1(), i.GetArgoRolloutsInformerFactory().Argoproj().V1alpha1().Rollouts()\n\treturn getWorkload(ai, ri, name, namespace, workloadKind)\n}\n\nfunc getWorkload(ai apps.Interface, ri argorollouts.RolloutInformer, name, namespace string, kind k8sapi.Kind) (obj k8sapi.Workload, err error) {\n\tswitch kind {\n\tcase k8sapi.DeploymentKind:\n\t\treturn getDeployment(ai, name, namespace)\n\tcase k8sapi.ReplicaSetKind:\n\t\treturn getReplicaSet(ai, name, namespace)\n\tcase k8sapi.StatefulSetKind:\n\t\treturn getStatefulSet(ai, name, namespace)\n\tcase k8sapi.RolloutKind:\n\t\treturn getRollout(ri, name, namespace)\n\tcase \"\":\n\t\tfor _, wk := range k8sapi.KnownWorkloadKinds {\n\t\t\tif obj, err = getWorkload(ai, ri, name, namespace, wk); err == nil {\n\t\t\t\treturn obj, nil\n\t\t\t}\n\t\t\tif !k8sErrors.IsNotFound(err) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\treturn nil, k8sErrors.NewNotFound(core.Resource(\"workload\"), name+\".\"+namespace)\n\tdefault:\n\t\treturn nil, k8sapi.UnsupportedWorkloadKindError(kind)\n\t}\n}\n\nfunc getDeployment(ai apps.Interface, name, namespace string) (wl k8sapi.Workload, err error) {\n\tdep, err := ai.Deployments().Lister().Deployments(namespace).Get(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn k8sapi.Deployment(dep), nil\n}\n\nfunc getRollout(ri argorollouts.RolloutInformer, name, namespace string) (wl k8sapi.Workload, err error) {\n\tif ri == nil {\n\t\treturn nil, k8sapi.UnsupportedWorkloadKindError(\"Rollout\")\n\t}\n\trollout, err := ri.Lister().Rollouts(namespace).Get(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn k8sapi.Rollout(rollout), nil\n}\n\nfunc getReplicaSet(ai apps.Interface, name, namespace string) (k8sapi.Workload, error) {\n\trs, err := ai.ReplicaSets().Lister().ReplicaSets(namespace).Get(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn k8sapi.ReplicaSet(rs), nil\n}\n\nfunc getStatefulSet(ai apps.Interface, name, namespace string) (k8sapi.Workload, error) {\n\tss, err := ai.StatefulSets().Lister().StatefulSets(namespace).Get(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn k8sapi.StatefulSet(ss), nil\n}\n\nfunc FindServicesForPod(ctx context.Context, pod *core.PodTemplateSpec, svcName string) ([]k8sapi.Object, error) {\n\tswitch {\n\tcase svcName != \"\":\n\t\tvar svc *core.Service\n\t\tvar err error\n\t\tif f := informer.GetK8sFactory(ctx, pod.Namespace); f != nil {\n\t\t\tsvc, err = f.Core().V1().Services().Lister().Services(pod.Namespace).Get(svcName)\n\t\t} else {\n\t\t\t// This shouldn't happen really.\n\t\t\tclog.Debugf(ctx, \"fetching service %s.%s using direct API call\", svcName, pod.Namespace)\n\t\t\tsvc, err = k8sapi.GetK8sInterface(ctx).CoreV1().Services(pod.Namespace).Get(ctx, svcName, meta.GetOptions{})\n\t\t}\n\t\tif err != nil {\n\t\t\tif k8sErrors.IsNotFound(err) {\n\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\"unable to find service %s specified by annotation %s declared in pod %s.%s\",\n\t\t\t\t\tsvcName, annotation.InjectServiceName, pod.Name, pod.Namespace)\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []k8sapi.Object{k8sapi.Service(svc)}, nil\n\tcase len(pod.Labels) > 0:\n\t\treturn findServicesSelecting(ctx, pod.Namespace, labels.Set(pod.Labels))\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unable to find a service using pod %s.%s because it has no labels\", pod.Name, pod.Namespace)\n\t}\n}\n\ntype objectsStringer []k8sapi.Object\n\nfunc (os objectsStringer) String() string {\n\tb := bytes.Buffer{}\n\tl := len(os)\n\tif l == 0 {\n\t\treturn \"no services\"\n\t}\n\tfor i, o := range os {\n\t\tif i > 0 {\n\t\t\tif l != 2 {\n\t\t\t\tb.WriteString(\", \")\n\t\t\t}\n\t\t\tif i == l-1 {\n\t\t\t\tb.WriteString(\" and \")\n\t\t\t}\n\t\t}\n\t\tb.WriteString(o.GetName())\n\t}\n\treturn b.String()\n}\n\n// findServicesSelecting finds all services that has a selector that matches the given labels.\nfunc findServicesSelecting(ctx context.Context, namespace string, lbs labels.Labels) ([]k8sapi.Object, error) {\n\tvar ms []k8sapi.Object\n\tvar scanned int\n\tif f := informer.GetK8sFactory(ctx, namespace); f != nil {\n\t\tss, err := f.Core().V1().Services().Lister().Services(namespace).List(labels.Everything())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tscanned = len(ss)\n\t\tfor _, s := range ss {\n\t\t\tsel := s.Spec.Selector\n\t\t\tif len(sel) > 0 && labels.SelectorFromValidatedSet(sel).Matches(lbs) {\n\t\t\t\tms = append(ms, k8sapi.Service(s))\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// This shouldn't happen really.\n\t\tclog.Tracef(ctx, \"Fetching services in %s using direct API call\", namespace)\n\t\tl, err := k8sapi.GetK8sInterface(ctx).CoreV1().Services(namespace).List(ctx, meta.ListOptions{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\titems := l.Items\n\t\tscanned = len(items)\n\t\tfor i := range items {\n\t\t\ts := &items[i]\n\t\t\tsel := s.Spec.Selector\n\t\t\tif len(sel) > 0 && labels.SelectorFromValidatedSet(sel).Matches(lbs) {\n\t\t\t\tms = append(ms, k8sapi.Service(s))\n\t\t\t}\n\t\t}\n\t}\n\t// Ensure predictable order of found services\n\tsort.Slice(ms, func(i, j int) bool {\n\t\treturn ms[i].GetName() < ms[j].GetName()\n\t})\n\tclog.Tracef(ctx, \"Scanned %d services in namespace %s and found that %s selects labels %v\", scanned, namespace, objectsStringer(ms), lbs)\n\treturn ms, nil\n}\n\n// findContainerMatchingPort finds the container that matches the given ServicePort. The match is\n// made using Protocol, and the Name or the ContainerPort field of each port in each container\n// depending on if  the service port is symbolic or numeric. The first container with a matching\n// port is returned along with the index of the container port that matched.\n//\n// The first container with no ports at all is returned together with a port index of -1, in case\n// no port match could be made and the service port is numeric. This enables intercepts of containers\n// that indeed do listen a port but lack a matching port description in the manifest, which is what\n// you get if you do:\n//\n//\tkubectl create deploy my-deploy --image my-image\n//\tkubectl expose deploy my-deploy --port 80 --target-port 8080\nfunc findContainerMatchingPort(port *core.ServicePort, cns []core.Container) (*core.Container, int) {\n\t// The protocol of the targetPort must match the protocol of the containerPort because it is\n\t// not illegal to listen with both TCP and UDP on the same port.\n\tproto := core.ProtocolTCP\n\tif port.Protocol != \"\" {\n\t\tproto = port.Protocol\n\t}\n\tprotoEqual := func(p core.Protocol) bool {\n\t\treturn p == proto || p == \"\" && proto == core.ProtocolTCP\n\t}\n\n\tif port.TargetPort.Type == intstr.String {\n\t\tportName := port.TargetPort.StrVal\n\t\tfor ci := range cns {\n\t\t\tcn := &cns[ci]\n\t\t\tfor pi := range cn.Ports {\n\t\t\t\tp := &cn.Ports[pi]\n\t\t\t\tif p.Name == portName && protoEqual(p.Protocol) {\n\t\t\t\t\treturn cn, pi\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\tportNum := port.TargetPort.IntVal\n\t\tif portNum == 0 {\n\t\t\t// The targetPort default is the value of the port field.\n\t\t\tportNum = port.Port\n\t\t}\n\t\tfor ci := range cns {\n\t\t\tcn := &cns[ci]\n\t\t\tfor pi := range cn.Ports {\n\t\t\t\tp := &cn.Ports[pi]\n\t\t\t\tif p.ContainerPort == portNum && protoEqual(p.Protocol) {\n\t\t\t\t\treturn cn, pi\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// As a last resort, also consider containers that don't expose their ports at all. Those\n\t\t// containers match all ports because it's unknown what they might be listening to.\n\t\tfor ci := range cns {\n\t\t\tcn := &cns[ci]\n\t\t\tif len(cn.Ports) == 0 {\n\t\t\t\treturn cn, -1\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, 0\n}\n\n// IsPodRunning returns true if at least one container has state Running and a non-zero StartedAt.\nfunc IsPodRunning(pod *core.Pod) bool {\n\tfor _, cn := range pod.Status.ContainerStatuses {\n\t\tif r := cn.State.Running; r != nil && !r.StartedAt.IsZero() {\n\t\t\t// At least one container is running.\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// AgentContainer returns the pod's traffic-agent container, or nil if the pod doesn't have a traffic-agent.\nfunc AgentContainer(pod *core.Pod) *core.Container {\n\treturn containerByName(agentconfig.ContainerName, pod.Spec.Containers)\n}\n\n// InitContainer returns the pod's tel-agent-init init-container, or nil if the pod doesn't have a tel-agent-init.\nfunc InitContainer(pod *core.Pod) *core.Container {\n\treturn containerByName(agentconfig.InitContainerName, pod.Spec.InitContainers)\n}\n\nfunc containerByName(name string, cns []core.Container) *core.Container {\n\tfor i := range cns {\n\t\tcn := &cns[i]\n\t\tif cn.Name == name {\n\t\t\treturn cn\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/agentmap/generator.go",
    "content": "package agentmap\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nvar TrafficManagerSelector = labels.SelectorFromSet(map[string]string{ //nolint:gochecknoglobals // constant\n\t\"app\":          agentconfig.ManagerAppName,\n\t\"telepresence\": \"manager\",\n})\n\ntype GeneratorConfig struct {\n\tManagerPort         uint16\n\tAgentPort           uint16\n\tAPIPort             uint16\n\tQualifiedAgentImage string\n\tManagerNamespace    string\n\tLogLevel            slog.Level\n\tInitResources       *core.ResourceRequirements\n\tResources           *core.ResourceRequirements\n\tPullPolicy          string\n\tPullSecrets         []core.LocalObjectReference\n\tSecurityContext     *core.SecurityContext\n\tInitSecurityContext *core.SecurityContext\n\tMountPolicies       types.MountPolicies\n\tClientConnectionTTL time.Duration\n\tWatchRetryInterval  time.Duration\n\tEnableH2cProbing    bool\n\tEnableMetrics       bool\n}\n\nfunc portsFromContainerPortsAnnotation(ctx context.Context, wl k8sapi.Workload) (ports []types.PortIdentifier, err error) {\n\tpod := wl.GetPodTemplate()\n\tcpa := annotation.GetAnnotation(ctx, pod.GetAnnotations(), annotation.InjectContainerPorts, annotation.LegacyInjectContainerPorts)\n\tswitch cpa {\n\tcase \"\":\n\t\treturn nil, nil\n\tcase \"all\":\n\t\tcns := pod.Spec.Containers\n\t\tfor i := range cns {\n\t\t\tfor _, pn := range cns[i].Ports {\n\t\t\t\tpi := pn.Name\n\t\t\t\tif pi == \"\" {\n\t\t\t\t\tpi = strconv.Itoa(int(pn.ContainerPort))\n\t\t\t\t}\n\t\t\t\tif pn.Protocol != core.ProtocolTCP {\n\t\t\t\t\tpi += \"/\" + string(pn.Protocol)\n\t\t\t\t}\n\t\t\t\tports = append(ports, types.PortIdentifier(pi))\n\t\t\t}\n\t\t}\n\tdefault:\n\t\tports, err = portsFromAnnotationValue(wl, annotation.InjectContainerPorts, cpa)\n\t}\n\treturn ports, err\n}\n\nfunc portsFromAnnotationValue(wl k8sapi.Workload, annotation, value string) (ports []types.PortIdentifier, err error) {\n\tcps := strings.Split(value, \",\")\n\tports = make([]types.PortIdentifier, len(cps))\n\tfor i, cp := range cps {\n\t\tpi := types.PortIdentifier(cp)\n\t\tif err = pi.Validate(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse annotation %s of %s: %w\", annotation, wl, err)\n\t\t}\n\t\tports[i] = pi\n\t}\n\treturn ports, nil\n}\n\nfunc (cfg *GeneratorConfig) Generate(\n\tctx context.Context,\n\twl k8sapi.Workload,\n\texistingConfig *agentconfig.Sidecar,\n) (*agentconfig.Sidecar, error) {\n\tif TrafficManagerSelector.Matches(labels.Set(wl.GetLabels())) {\n\t\treturn nil, fmt.Errorf(\"%s is the Telepresence Traffic Manager. It can not have a traffic-agent\", wl)\n\t}\n\n\tpod := wl.GetPodTemplate()\n\tpod.Namespace = wl.GetNamespace()\n\tcns := pod.Spec.Containers\n\tfor i := range cns {\n\t\tcn := &cns[i]\n\t\tif cn.Name == agentconfig.ContainerName {\n\t\t\tcontinue\n\t\t}\n\t\tports := cn.Ports\n\t\tfor pi := range ports {\n\t\t\tif ports[pi].ContainerPort == int32(cfg.AgentPort) {\n\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\"the %s.%s pod container %s is exposing the same port (%d) as the %s sidecar\",\n\t\t\t\t\tpod.Name, pod.Namespace, cn.Name, cfg.AgentPort, agentconfig.ContainerName)\n\t\t\t}\n\t\t}\n\t}\n\n\tann := annotation.GetAnnotation(ctx, pod.Annotations, annotation.InjectServiceName, annotation.LegacyInjectServiceName)\n\tsvcs, err := FindServicesForPod(ctx, pod, ann)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpns := make(map[int32]uint16)\n\tagentPortNumberFunc := func(cnPort int32) uint16 {\n\t\tif p, ok := pns[cnPort]; ok {\n\t\t\t// Port already mapped. Reuse that mapping\n\t\t\treturn p\n\t\t}\n\t\tp := cfg.AgentPort + uint16(len(pns))\n\t\tpns[cnPort] = p\n\t\treturn p\n\t}\n\n\tvar ports []types.PortIdentifier\n\tann = annotation.GetAnnotation(ctx, pod.Annotations, annotation.InjectServicePorts, annotation.LegacyInjectServicePort)\n\tif ann != \"\" {\n\t\tports, err = portsFromAnnotationValue(wl, annotation.InjectServicePorts, ann)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcfg.MountPolicies, err = cfg.MountPolicies.AddAnnotations(ctx, pod.Annotations)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar ccs []*agentconfig.Container\n\tfor _, svc := range svcs {\n\t\tsvcImpl, _ := k8sapi.ServiceImpl(svc)\n\t\tccs = cfg.appendAgentContainerConfigs(svcImpl, pod, ports, agentPortNumberFunc, ccs, existingConfig)\n\t}\n\n\tports, err = portsFromContainerPortsAnnotation(ctx, wl)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(ports) > 0 {\n\t\tif ccs, err = cfg.appendServiceLessAgentContainerConfigs(pod, ports, agentPortNumberFunc, ccs, existingConfig); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Append other containers even though they aren't directly interceptable. They might be fronted by a\n\t// dispatching container that is, or they might be candidates for `ingest`.\n\tfor i := range cns {\n\t\tcn := &cns[i]\n\t\tif cn.Name == agentconfig.ContainerName {\n\t\t\tcontinue\n\t\t}\n\t\tif !slices.ContainsFunc(ccs, func(cc *agentconfig.Container) bool { return cc.Name == cn.Name }) {\n\t\t\tccs = append(ccs, cfg.newContainerConfig(cn, len(ccs), nil, containerReplacePolicy(existingConfig, cn)))\n\t\t}\n\t}\n\n\treturn &agentconfig.Sidecar{\n\t\tAgentImage:          cfg.QualifiedAgentImage,\n\t\tAgentName:           wl.GetName(),\n\t\tLogLevel:            cfg.LogLevel,\n\t\tNamespace:           wl.GetNamespace(),\n\t\tWorkloadName:        wl.GetName(),\n\t\tWorkloadKind:        wl.GetKind(),\n\t\tManagerHost:         agentconfig.ManagerAppName + \".\" + cfg.ManagerNamespace,\n\t\tManagerPort:         cfg.ManagerPort,\n\t\tAPIPort:             cfg.APIPort,\n\t\tClientConnectionTTL: cfg.ClientConnectionTTL,\n\t\tMountPolicies:       cfg.MountPolicies,\n\t\tContainers:          ccs,\n\t\tInitResources:       cfg.InitResources,\n\t\tResources:           cfg.Resources,\n\t\tPullPolicy:          cfg.PullPolicy,\n\t\tPullSecrets:         cfg.PullSecrets,\n\t\tSecurityContext:     cfg.SecurityContext,\n\t\tInitSecurityContext: cfg.InitSecurityContext,\n\t\tEnableH2cProbing:    cfg.EnableH2cProbing,\n\t\tEnableMetrics:       cfg.EnableMetrics,\n\t\tWatchRetryInterval:  cfg.WatchRetryInterval,\n\t}, nil\n}\n\nfunc (cfg *GeneratorConfig) appendAgentContainerConfigs(\n\tsvc *core.Service,\n\tpod *core.PodTemplateSpec,\n\tportAnnotations []types.PortIdentifier,\n\tagentPortNumberFunc func(int32) uint16,\n\tccs []*agentconfig.Container,\n\texistingConfig *agentconfig.Sidecar,\n) []*agentconfig.Container {\n\tports := filterServicePorts(svc, portAnnotations)\nnextSvcPort:\n\tfor _, port := range ports {\n\t\tcn, i := findContainerMatchingPort(&port, pod.Spec.Containers)\n\t\tif cn == nil || cn.Name == agentconfig.ContainerName {\n\t\t\tcontinue\n\t\t}\n\t\tvar appPort core.ContainerPort\n\t\tif i < 0 {\n\t\t\t// Can only happen if the service port is numeric, so it's safe to use TargetPort.IntVal here\n\t\t\tappPort = core.ContainerPort{\n\t\t\t\tProtocol:      port.Protocol,\n\t\t\t\tContainerPort: port.TargetPort.IntVal,\n\t\t\t}\n\t\t} else {\n\t\t\tappPort = cn.Ports[i]\n\t\t}\n\n\t\tic := &agentconfig.Intercept{\n\t\t\tServiceName:       svc.Name,\n\t\t\tServiceUID:        svc.UID,\n\t\t\tServicePortName:   port.Name,\n\t\t\tServicePort:       uint16(port.Port),\n\t\t\tTargetPortNumeric: port.TargetPort.Type == intstr.Int,\n\t\t\tProtocol:          types.FromK8sProtocol(port.Protocol),\n\t\t\tAgentPort:         agentPortNumberFunc(appPort.ContainerPort),\n\t\t\tContainerPortName: appPort.Name,\n\t\t\tContainerPort:     uint16(appPort.ContainerPort),\n\t\t}\n\t\tif port.AppProtocol != nil {\n\t\t\tic.AppProtocol = *port.AppProtocol\n\t\t}\n\n\t\t// The container might already have intercepts declared\n\t\tfor _, cc := range ccs {\n\t\t\tif cc.Name == cn.Name {\n\t\t\t\tcc.Intercepts = append(cc.Intercepts, ic)\n\t\t\t\tcontinue nextSvcPort\n\t\t\t}\n\t\t}\n\t\tccs = append(ccs, cfg.newContainerConfig(cn, len(ccs), []*agentconfig.Intercept{ic}, containerReplacePolicy(existingConfig, cn)))\n\t}\n\treturn ccs\n}\n\nfunc (cfg *GeneratorConfig) newContainerConfig(cn *core.Container, index int, ics []*agentconfig.Intercept, rp agentconfig.ReplacePolicy) *agentconfig.Container {\n\t// Create the concrete MountPolicies that map mount path to policy\n\tvar mps types.MountPolicies\n\tfor i := range cn.VolumeMounts {\n\t\tvm := &cn.VolumeMounts[i]\n\t\tpath := vm.MountPath\n\t\tvp := cfg.MountPolicies.Get(vm.Name, path)\n\t\tif vp != types.MountPolicyIgnore {\n\t\t\tif mps == nil {\n\t\t\t\tmps = make(types.MountPolicies, len(cn.VolumeMounts))\n\t\t\t}\n\t\t\tmps[path] = vp\n\t\t}\n\t}\n\t// Legacy mounts property must list all remote mounts, no more and no less\n\tvar mounts []string\n\tif len(mps) > 0 {\n\t\tmounts = make([]string, 0, len(mps))\n\t\tfor key, mp := range mps {\n\t\t\tif mp == types.MountPolicyRemote || mp == types.MountPolicyRemoteReadOnly {\n\t\t\t\tmounts = append(mounts, key)\n\t\t\t}\n\t\t}\n\t}\n\tsort.Strings(mounts)\n\treturn &agentconfig.Container{\n\t\tName:       cn.Name,\n\t\tEnvPrefix:  CapsBase26(uint64(index)) + \"_\",\n\t\tMountPoint: agentconfig.MountPrefixApp + \"/\" + cn.Name,\n\t\tMountPaths: mounts,\n\t\tMounts:     mps,\n\t\tIntercepts: ics,\n\t\tReplace:    rp,\n\t}\n}\n\nfunc findContainerPort(cns []core.Container, p types.PortIdentifier) (*core.Container, *core.ContainerPort) {\n\tproto, name, num := p.ProtoAndNameOrNumber()\n\tfor n := range cns {\n\t\tcn := &cns[n]\n\t\tif cn.Name != agentconfig.ContainerName {\n\t\t\tfor i := range cn.Ports {\n\t\t\t\tappPort := &cn.Ports[i]\n\t\t\t\tif (name != \"\" && name == appPort.Name || num == uint16(appPort.ContainerPort)) &&\n\t\t\t\t\t(proto.String() == string(appPort.Protocol) || proto == types.ProtoTCP && appPort.Protocol == \"\") {\n\t\t\t\t\treturn cn, appPort\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc (cfg *GeneratorConfig) appendServiceLessAgentContainerConfigs(\n\tpod *core.PodTemplateSpec,\n\tportAnnotations []types.PortIdentifier,\n\tagentPortNumberFunc func(int32) uint16,\n\tccs []*agentconfig.Container,\n\texistingConfig *agentconfig.Sidecar,\n) ([]*agentconfig.Container, error) {\n\tcns := pod.Spec.Containers\n\tanonNameIndex := uint64(0)\nnextContainerPort:\n\tfor _, p := range portAnnotations {\n\t\tcn, appPort := findContainerPort(cns, p)\n\t\tif appPort == nil {\n\t\t\t// The port is not explicitly declared as a container port, so if possible, we synthesize one.\n\t\t\tproto, name, num := p.ProtoAndNameOrNumber()\n\t\t\tif name != \"\" {\n\t\t\t\t// We can only synthesize given a numeric port.\n\t\t\t\treturn nil, fmt.Errorf(\"found no container port that matches port annotation %s\", p)\n\t\t\t}\n\t\t\tappPort = &core.ContainerPort{\n\t\t\t\tName:          fmt.Sprintf(\"port-%s\", Base26(anonNameIndex)),\n\t\t\t\tContainerPort: int32(num),\n\t\t\t\tProtocol:      core.Protocol(proto.String()),\n\t\t\t}\n\t\t\tanonNameIndex++\n\t\t}\n\t\tic := &agentconfig.Intercept{\n\t\t\tTargetPortNumeric: true,\n\t\t\tProtocol:          types.FromK8sProtocol(appPort.Protocol),\n\t\t\tAgentPort:         agentPortNumberFunc(appPort.ContainerPort),\n\t\t\tContainerPortName: appPort.Name,\n\t\t\tContainerPort:     uint16(appPort.ContainerPort),\n\t\t}\n\n\t\t// The container might already have intercepts declared\n\t\tfor _, cc := range ccs {\n\t\t\tif cc.Name == cn.Name {\n\t\t\t\t// Don't add service-less intercept if an intercept with a service is present\n\t\t\t\tcnFound := false\n\t\t\t\tfor _, eic := range cc.Intercepts {\n\t\t\t\t\tif eic.ContainerPort == ic.ContainerPort {\n\t\t\t\t\t\tcnFound = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !cnFound {\n\t\t\t\t\tcc.Intercepts = append(cc.Intercepts, ic)\n\t\t\t\t}\n\t\t\t\tcontinue nextContainerPort\n\t\t\t}\n\t\t}\n\t\tccs = append(ccs, cfg.newContainerConfig(cn, len(ccs), []*agentconfig.Intercept{ic}, containerReplacePolicy(existingConfig, cn)))\n\t}\n\treturn ccs, nil\n}\n\nfunc containerReplacePolicy(existingConfig *agentconfig.Sidecar, cn *core.Container) agentconfig.ReplacePolicy {\n\tvar replaceContainer agentconfig.ReplacePolicy\n\tif existingConfig != nil {\n\t\tfor _, cc := range existingConfig.Containers {\n\t\t\tif cc.Name == cn.Name {\n\t\t\t\treplaceContainer = cc.Replace\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn replaceContainer\n}\n\n// filterServicePorts iterates through a list of ports in a service and\n// only returns the ports that match the given nameOrNumber. All ports will\n// be returned if nameOrNumber is equal to the empty string.\nfunc filterServicePorts(svc *core.Service, portAnnotations []types.PortIdentifier) []core.ServicePort {\n\tports := svc.Spec.Ports\n\tif len(portAnnotations) == 0 {\n\t\treturn ports\n\t}\n\tsvcPorts := make([]core.ServicePort, 0)\n\tfor _, pi := range portAnnotations {\n\t\tproto, name, num := pi.ProtoAndNameOrNumber()\n\t\tif name != \"\" {\n\t\t\tfor _, port := range ports {\n\t\t\t\tif port.Name == name {\n\t\t\t\t\tsvcPorts = append(svcPorts, port)\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, port := range ports {\n\t\t\t\tpn := int32(0)\n\t\t\t\tif port.TargetPort.Type == intstr.Int {\n\t\t\t\t\tpn = port.TargetPort.IntVal\n\t\t\t\t}\n\t\t\t\tif pn == 0 {\n\t\t\t\t\tpn = port.Port\n\t\t\t\t}\n\t\t\t\tif uint16(pn) == num && types.FromK8sProtocol(port.Protocol) == proto {\n\t\t\t\t\tsvcPorts = append(svcPorts, port)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn svcPorts\n}\n"
  },
  {
    "path": "pkg/annotation/annotation.go",
    "content": "package annotation\n\nimport (\n\t\"context\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nconst (\n\tDomainPrefix = \"telepresence.io/\"\n\n\tConfig                     = DomainPrefix + \"agent-config\"\n\tInjectContainerPorts       = DomainPrefix + \"inject-container-ports\"\n\tInjectIgnoreVolumeMounts   = DomainPrefix + \"inject-ignore-volume-mounts\"\n\tInjectServiceName          = DomainPrefix + \"inject-service-name\"\n\tInjectServicePorts         = DomainPrefix + \"inject-service-ports\"\n\tInjectTrafficAgent         = DomainPrefix + \"inject-traffic-agent\"\n\tManuallyInjected           = DomainPrefix + \"manually-injected\"\n\tReplacedContainerPrefix    = DomainPrefix + \"replaced-container.\"\n\tRestartedAt                = DomainPrefix + \"restartedAt\"\n\tVolumeMountPolicies        = DomainPrefix + \"mount-policies\"\n\tDownstreamTLSSecret        = DomainPrefix + \"downstream-tls-secret\"\n\tDownstreamCertificatePath  = DomainPrefix + \"downstream-tls-path\"\n\tUpstreamTLSSecret          = DomainPrefix + \"upstream-tls-secret\"\n\tUpstreamCertificatePath    = DomainPrefix + \"upstream-tls-path\"\n\tUpstreamInsecureSkipVerify = DomainPrefix + \"upstream-insecure-skip-verify\"\n\tUpstreamProbeTimeout       = DomainPrefix + \"upstream-probe-timeout\"\n\n\tLegacyDomainPrefix             = \"telepresence.getambassador.io/\"\n\tLegacyInjectContainerPorts     = LegacyDomainPrefix + \"inject-container-ports\"\n\tLegacyInjectIgnoreVolumeMounts = LegacyDomainPrefix + \"inject-ignore-volume-mounts\"\n\tLegacyInjectServiceName        = LegacyDomainPrefix + \"inject-service-name\"\n\tLegacyInjectServicePort        = LegacyDomainPrefix + \"inject-service-port\"\n\tLegacyInjectTrafficAgent       = LegacyDomainPrefix + \"inject-traffic-agent\"\n\tLegacyManuallyInjected         = LegacyDomainPrefix + \"manually-injected\"\n)\n\nfunc GetAnnotation(ctx context.Context, annotations map[string]string, key, deprecatedKey string) string {\n\tvalue, ok := annotations[key]\n\tif !ok {\n\t\tvalue, ok = annotations[deprecatedKey]\n\t\tif ok {\n\t\t\tclog.Warnf(ctx, \"Annotation %q is deprecated. Use %q instead\", key, value)\n\t\t}\n\t}\n\treturn value\n}\n\nfunc ReplaceAnnotationKey(cn string) string {\n\treturn ReplacedContainerPrefix + cn\n}\n"
  },
  {
    "path": "pkg/authenticator/authenticator.go",
    "content": "package authenticator\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tclientcmd_api \"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\nfunc NewService(\n\tclientConfigProvider k8sapi.ClientConfigProvider,\n) *Service {\n\treturn &Service{\n\t\tclientConfigProvider: clientConfigProvider,\n\t}\n}\n\ntype ExecCredentialsResolver interface {\n\tResolve(\n\t\tctx context.Context,\n\t\texecConfig *clientcmd_api.ExecConfig,\n\t) ([]byte, error)\n}\n\ntype Service struct {\n\tclientConfigProvider k8sapi.ClientConfigProvider\n}\n\nfunc (a Service) GetExecCredentials(ctx context.Context, contextName string) ([]byte, error) {\n\texecConfig, err := a.getExecConfigFromContext(contextName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get exec config from context %s, %w\", contextName, err)\n\t}\n\n\trawExecCredentials, err := ResolveExecConfig(ctx, execConfig)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to resolve credentials: %w\", err)\n\t}\n\n\treturn rawExecCredentials, nil\n}\n\nfunc (a Service) getExecConfigFromContext(contextName string) (*clientcmd_api.ExecConfig, error) {\n\tcc, err := a.clientConfigProvider.ClientConfig()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get kubeconfig provider: %w\", err)\n\t}\n\trawConfig, err := cc.RawConfig()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get kubeconfig: %w\", err)\n\t}\n\n\tkubeContext, ok := rawConfig.Contexts[contextName]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"kube context %s doesn't exist\", contextName)\n\t}\n\n\tauthInfo, ok := rawConfig.AuthInfos[kubeContext.AuthInfo]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"auth info %s doesn't exist\", kubeContext.AuthInfo)\n\t}\n\n\tif authInfo.Exec == nil {\n\t\treturn nil, fmt.Errorf(\"auth info %s isn't of type exec\", kubeContext.AuthInfo)\n\t}\n\n\treturn &clientcmd_api.ExecConfig{\n\t\tCommand: authInfo.Exec.Command,\n\t\tArgs:    authInfo.Exec.Args,\n\t\tEnv:     authInfo.Exec.Env,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/authenticator/config.go",
    "content": "package authenticator\n\nimport \"k8s.io/client-go/tools/clientcmd\"\n\nfunc LoadKubeConfig(kubeConfig string) clientcmd.ClientConfig {\n\tloadingRules := clientcmd.NewDefaultClientConfigLoadingRules()\n\tloadingRules.ExplicitPath = kubeConfig\n\n\treturn clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{})\n}\n"
  },
  {
    "path": "pkg/authenticator/exec.go",
    "content": "package authenticator\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\n\tclientcmdapi \"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nfunc ResolveExecConfig(ctx context.Context, execConfig *clientcmdapi.ExecConfig) ([]byte, error) {\n\tvar buf bytes.Buffer\n\n\tcmd := proc.CommandContext(ctx, execConfig.Command, execConfig.Args...)\n\tcmd.Stdout = &buf\n\tcmd.Stderr = dos.Stderr(ctx)\n\tcmd.Env = dos.Environ(ctx)\n\tif len(execConfig.Env) > 0 {\n\t\tem := dos.FromEnvPairs(cmd.Env)\n\t\tfor _, ev := range execConfig.Env {\n\t\t\tem[ev.Name] = ev.Value\n\t\t}\n\t\tcmd.Env = em.Environ()\n\t}\n\n\tif err := cmd.Run(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to run host command: %w\", err)\n\t}\n\n\treturn buf.Bytes(), nil\n}\n"
  },
  {
    "path": "pkg/authenticator/exec_test.go",
    "content": "package authenticator\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tclientcmdapi \"k8s.io/client-go/tools/clientcmd/api\"\n)\n\nfunc TestExecCredentialsNoLocalEnv(t *testing.T) {\n\tt.Setenv(\"GLOBAL_ENV\", \"global-val\")\n\n\tconfig := &clientcmdapi.ExecConfig{\n\t\tCommand: \"sh\",\n\t\tArgs:    []string{\"-c\", \"echo $GLOBAL_ENV/$LOCAL_ENV\"},\n\t}\n\tresult, err := ResolveExecConfig(context.Background(), config)\n\tassert.NoError(t, err)\n\tassert.Equal(t, string(result), \"global-val/\\n\")\n}\n\nfunc TestExecCredentialsYesLocalEnv(t *testing.T) {\n\tt.Setenv(\"GLOBAL_ENV\", \"global-val\")\n\n\tconfig := &clientcmdapi.ExecConfig{\n\t\tCommand: \"sh\",\n\t\tArgs:    []string{\"-c\", \"echo $GLOBAL_ENV/$LOCAL_ENV\"},\n\t\tEnv:     []clientcmdapi.ExecEnvVar{{Name: \"LOCAL_ENV\", Value: \"local-val\"}},\n\t}\n\tresult, err := ResolveExecConfig(context.Background(), config)\n\tassert.NoError(t, err)\n\tassert.Equal(t, string(result), \"global-val/local-val\\n\")\n}\n"
  },
  {
    "path": "pkg/authenticator/grpc/authenticator.go",
    "content": "package grpc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/authenticator\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/authenticator\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\nfunc RegisterAuthenticatorServer(srv *grpc.Server, clientConfigProvider k8sapi.ClientConfigProvider) {\n\trpc.RegisterAuthenticatorServer(srv, &AuthenticatorServer{\n\t\tauthenticator: authenticator.NewService(clientConfigProvider),\n\t})\n}\n\ntype Authenticator interface {\n\tGetExecCredentials(ctx context.Context, contextName string) ([]byte, error)\n}\n\ntype AuthenticatorServer struct {\n\trpc.UnsafeAuthenticatorServer\n\n\tauthenticator Authenticator\n}\n\n// GetContextExecCredentials returns credentials for a particular Kubernetes context on the host machine.\nfunc (h *AuthenticatorServer) GetContextExecCredentials(ctx context.Context, request *rpc.GetContextExecCredentialsRequest) (*rpc.GetContextExecCredentialsResponse, error) {\n\tclog.Debugf(ctx, \"GetContextExecCredentials(%s)\", request.ContextName)\n\trawExecCredentials, err := h.authenticator.GetExecCredentials(ctx, request.ContextName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to resolve exec credentils: %w\", err)\n\t}\n\n\treturn &rpc.GetContextExecCredentialsResponse{\n\t\tRawCredentials: rawExecCredentials,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/authenticator/patcher/patcher.go",
    "content": "package patcher\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tclientcmdapi \"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n)\n\nconst (\n\tkubeConfigStubSubCommands = \"kubeauth\"\n)\n\n// AddressProvider is a function that returns the path to the telepresence executable and an address to a service that\n// implements the Authenticator gRPC.\n//\n// The function will typically start the gRPC service, and the service is therefore given\n// a list of files that it must listen to in order to reliably resolve requests. It is\n// also passed a pointer to the minified config that will be stored in a file so that it\n// has a chance to modify it.\ntype (\n\tAddressProvider func(configFiles []string) (executable, addr, configFile string, err error)\n\tPatcher         func(*clientcmdapi.Config) error\n)\n\n// CreateExternalKubeConfig will load the current kubeconfig and minimize it so that it just contains the current\n// context. Exec configs in that context are replaced by a stub binary that calls the kubeauth service. The kubeauth\n// service, which runs on the host with the user's credentials, will then use the original Exec config.\n// The minified config is stored in the <telepresence cache>/kube directory and returned.\nfunc CreateExternalKubeConfig(\n\tctx context.Context,\n\tloader clientcmd.ClientConfig,\n\tkubeContext string,\n\tauthAddressFunc AddressProvider,\n\tpatcher Patcher,\n) ([]byte, error) {\n\tns, _, err := loader.Namespace()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfigFiles := loader.ConfigAccess().GetLoadingPrecedence()\n\tclog.Debugf(ctx, \"host kubeconfig = %v\", configFiles)\n\torigConfig, err := loader.RawConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar config clientcmdapi.Config\n\torigConfig.DeepCopyInto(&config)\n\n\t// Minify the config so that we only deal with the current context.\n\tif kubeContext != \"\" {\n\t\tconfig.CurrentContext = kubeContext\n\t}\n\tif err = clientcmdapi.MinifyConfig(&config); err != nil {\n\t\treturn nil, err\n\t}\n\tclog.Debugf(ctx, \"context = %q, namespace %q\", config.CurrentContext, ns)\n\n\t// Minify guarantees that the CurrentContext is set, but not that it has a cluster\n\tcc := config.Contexts[config.CurrentContext]\n\tif cc.Cluster == \"\" {\n\t\treturn nil, fmt.Errorf(\"current context %q has no cluster\", config.CurrentContext)\n\t}\n\n\tif needsStubbedExec(&config) {\n\t\texecutable, addr, configFile, err := authAddressFunc(configFiles)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err = replaceAuthExecWithStub(&config, executable, addr, configFile); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// Ensure that all certs are embedded instead of reachable using a path\n\tif err = clientcmdapi.FlattenConfig(&config); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif patcher != nil {\n\t\tif err = patcher(&config); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn clientcmd.Write(config)\n}\n\n// replaceAuthExecWithStub goes through the kubeconfig and replaces all uses of the Exec auth method by\n// an invocation of the stub binary.\nfunc replaceAuthExecWithStub(rawConfig *clientcmdapi.Config, executable, address, configFile string) error {\n\tfor contextName, kubeContext := range rawConfig.Contexts {\n\t\t// Find related Auth.\n\t\tauthInfo, ok := rawConfig.AuthInfos[kubeContext.AuthInfo]\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"auth info %s not found for context %s\", kubeContext.AuthInfo, contextName)\n\t\t}\n\n\t\t// If it isn't an exec mode context, just return the default host kubeconfig.\n\t\tif authInfo.Exec == nil {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Patch exec.\n\t\tauthInfo.Exec = &clientcmdapi.ExecConfig{\n\t\t\tInteractiveMode: clientcmdapi.NeverExecInteractiveMode,\n\t\t\tAPIVersion:      authInfo.Exec.APIVersion,\n\t\t\tCommand:         executable,\n\t\t\tArgs:            []string{kubeConfigStubSubCommands, \"--\" + global.FlagConfig, configFile, contextName, address},\n\t\t}\n\t}\n\treturn nil\n}\n\n// needsStubbedExec returns true if the config contains at least one user with an Exec type AuthInfo.\nfunc needsStubbedExec(rawConfig *clientcmdapi.Config) bool {\n\tfor _, kubeContext := range rawConfig.Contexts {\n\t\tif authInfo, ok := rawConfig.AuthInfos[kubeContext.AuthInfo]; ok && authInfo.Exec != nil {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/cache/client.go",
    "content": "package cache\n\nimport (\n\t\"github.com/puzpuzpuz/xsync/v4\"\n)\n\ntype Entry[K comparable, V any] interface {\n\tKey() K\n\tValue() V\n}\n\ntype ClientMap[K comparable, V any] struct {\n\t*xsync.Map[K, V]\n}\n\nfunc (c *ClientMap[K, V]) Watch(doneCh <-chan struct{}, deltaCh <-chan Delta[K, V], onChanges func() error) error {\n\tfor {\n\t\tselect {\n\t\tcase <-doneCh:\n\t\t\treturn nil\n\t\tcase delta, ok := <-deltaCh:\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tfor k, v := range delta.Upserts {\n\t\t\t\tc.Store(k, v)\n\t\t\t}\n\t\t\tfor k := range delta.Removals {\n\t\t\t\tc.Delete(k)\n\t\t\t}\n\t\t\tif onChanges != nil {\n\t\t\t\terr := onChanges()\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc NewClientMap[K comparable, V any](options ...func(config *xsync.MapConfig)) *ClientMap[K, V] {\n\treturn &ClientMap[K, V]{Map: xsync.NewMap[K, V](options...)}\n}\n\ntype Server[K comparable, V any] interface {\n\tSubscribe(done <-chan struct{}, filter func(K, V) bool) <-chan Delta[K, V]\n}\n"
  },
  {
    "path": "pkg/cache/map.go",
    "content": "package cache\n\nimport (\n\t\"math\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/puzpuzpuz/xsync/v4\"\n)\n\ntype Delta[K comparable, V any] struct {\n\tUpserts  map[K]V\n\tRemovals map[K]V\n}\n\nfunc (delta *Delta[K, V]) Merge(other Delta[K, V]) {\n\tif len(delta.Upserts) == 0 {\n\t\tdelta.Upserts = other.Upserts\n\t} else {\n\t\tfor k, v := range other.Upserts {\n\t\t\tdelta.Upserts[k] = v\n\t\t}\n\t\tfor k := range other.Removals {\n\t\t\tdelete(delta.Upserts, k)\n\t\t}\n\t}\n\tif len(delta.Removals) == 0 {\n\t\tdelta.Removals = other.Removals\n\t} else {\n\t\tfor k, v := range other.Removals {\n\t\t\tdelta.Removals[k] = v\n\t\t}\n\t\tfor k := range other.Upserts {\n\t\t\tdelete(delta.Removals, k)\n\t\t}\n\t}\n}\n\ntype subscription[K comparable, V any] struct {\n\tchannel     chan Delta[K, V]\n\tinclude     func(K, V) bool\n\tinitialized atomic.Bool\n\tmark        atomic.Bool\n\tdoneCh      <-chan struct{}\n}\n\ntype Map[K comparable, V any] struct {\n\t*xsync.Map[K, V]\n\tsnapLock    sync.Mutex\n\tsnapshot    map[K]V\n\tequal       func(V, V) bool\n\tsubscribers *xsync.Map[uuid.UUID, *subscription[K, V]]\n\tnotifyDelay time.Duration\n\tnotifier    *time.Timer\n}\n\n// NewMap creates a new Map instance configured with the given options.\nfunc NewMap[K comparable, V any](equal func(V, V) bool, notifyDelay time.Duration, config ...func(*xsync.MapConfig)) *Map[K, V] {\n\tm := &Map[K, V]{\n\t\tMap:         xsync.NewMap[K, V](config...),\n\t\tequal:       equal,\n\t\tsubscribers: xsync.NewMap[uuid.UUID, *subscription[K, V]](),\n\t\tnotifyDelay: notifyDelay,\n\t}\n\tm.notifier = time.AfterFunc(math.MaxInt64, m.notify)\n\treturn m\n}\n\n// Subscribe returns a channel that will emit deltas that corresponds to modifications of the contained\n// values filtered by the given filter.\n//\n// The first delta is a snapshot of all values, and it is emitted immediately after the call to Subscribe().\n// After that, a new Delta is emitted then whenever the map changes a value for which the filter evaluates\n// to true.\n//\n// The values contained in a delta will reflect actual values in the map and must be considered immutable.\n// Mutating them will mutate the map without the map's knowledge and hence not trigger notifications to\n// subscribers.\n//\n// The returned channel will be closed when the given channel is closed.\nfunc (m *Map[K, V]) Subscribe(done <-chan struct{}, includeFilter func(K, V) bool) <-chan Delta[K, V] {\n\tch := make(chan Delta[K, V], 1)\n\tselect {\n\tcase <-done:\n\t\tclose(ch)\n\tdefault:\n\t\tm.notifier.Reset(math.MaxInt64)\n\n\t\tid := uuid.New()\n\t\tsb := &subscription[K, V]{include: includeFilter, channel: ch, doneCh: done}\n\t\tsb.mark.Store(true)\n\t\tm.subscribers.Store(id, sb)\n\n\t\t// Fire notifier immediately to send the snapshot.\n\t\tm.notify()\n\t\tgo func() {\n\t\t\t<-done\n\t\t\tm.subscribers.Delete(id)\n\t\t\tclose(ch)\n\t\t}()\n\t}\n\treturn ch\n}\n\n// Compute either sets the computed new value for the key or deletes the value for the key.\n// When the delete result of the valueFn function is set to true, the value will be deleted if it exists.\n// When delete is set to false, the value is updated to the newValue. The ok result indicates whether the\n// value was computed and stored, thus, is present in the map. The actual result contains the new value in\n// cases where the value was computed and stored. See the example for a few use cases.\n//\n// This call locks a hash table bucket while the compute function is executed. It means that modifications\n// on other entries in the bucket will be blocked until the valueFn executes. Consider this when the function\n// includes long-running operations.\nfunc (m *Map[K, V]) Compute(key K, f func(V, bool) (V, xsync.ComputeOp)) (V, bool) {\n\tmodified := false\n\tactual, ok := m.Map.Compute(key, func(v V, loaded bool) (V, xsync.ComputeOp) {\n\t\tfv, op := f(v, loaded)\n\t\tswitch op {\n\t\tcase xsync.CancelOp:\n\t\tcase xsync.UpdateOp:\n\t\t\tif loaded && m.equal(fv, v) {\n\t\t\t\tfv = v\n\t\t\t\top = xsync.CancelOp\n\t\t\t} else {\n\t\t\t\tmodified = true\n\t\t\t\tm.markSubscribers(key, fv)\n\t\t\t}\n\t\tcase xsync.DeleteOp:\n\t\t\tmodified = true\n\t\t\tm.markSubscribers(key, v)\n\t\t}\n\t\treturn fv, op\n\t})\n\tif modified {\n\t\tm.notifier.Reset(m.notifyDelay)\n\t}\n\treturn actual, ok\n}\n\n// CompareAndSwap checks if the current value for the given key equals the oldValue, and if\n// so, swaps the current value for the newValue.\n// The swapped result reports whether the value was swapped.\nfunc (m *Map[K, V]) CompareAndSwap(key K, oldValue, newValue V) (swapped bool) {\n\tm.Compute(key, func(cur V, loaded bool) (V, xsync.ComputeOp) {\n\t\tif loaded && m.equal(cur, oldValue) {\n\t\t\tswapped = true\n\t\t\treturn newValue, xsync.UpdateOp\n\t\t}\n\t\treturn oldValue, xsync.CancelOp\n\t})\n\treturn swapped\n}\n\n// Delete deletes the value for a key.\nfunc (m *Map[K, V]) Delete(key K) {\n\tm.Compute(key, func(oldValue V, loaded bool) (V, xsync.ComputeOp) {\n\t\tif !loaded {\n\t\t\treturn oldValue, xsync.CancelOp\n\t\t}\n\t\treturn oldValue, xsync.DeleteOp\n\t})\n}\n\n// LoadAll return a map of all entries.\nfunc (m *Map[K, V]) LoadAll() map[K]V {\n\tmr := make(map[K]V, m.Size())\n\tm.Range(func(key K, value V) bool {\n\t\tmr[key] = value\n\t\treturn true\n\t})\n\treturn mr\n}\n\n// LoadMatching return a map of all entries matching the given filter.\nfunc (m *Map[K, V]) LoadMatching(filter func(K, V) bool) map[K]V {\n\tmr := make(map[K]V)\n\tm.Range(func(key K, value V) bool {\n\t\tif filter(key, value) {\n\t\t\tmr[key] = value\n\t\t}\n\t\treturn true\n\t})\n\treturn mr\n}\n\n// LoadAndDelete deletes the value for a key, returning the previous value if any.\n// The loaded result reports whether the key was present.\nfunc (m *Map[K, V]) LoadAndDelete(key K) (previous V, loaded bool) {\n\tm.Compute(key, func(oldValue V, wasLoaded bool) (V, xsync.ComputeOp) {\n\t\tif wasLoaded {\n\t\t\tprevious = oldValue\n\t\t\tloaded = true\n\t\t\treturn oldValue, xsync.DeleteOp\n\t\t}\n\t\treturn oldValue, xsync.CancelOp\n\t})\n\treturn previous, loaded\n}\n\n// LoadAndStore stores a new value for the key and returns the existing one, if present. The loaded result is true if the\n// existing value was loaded, false otherwise.\nfunc (m *Map[K, V]) LoadAndStore(key K, value V) (existing V, loaded bool) {\n\tm.Compute(key, func(oldValue V, wasLoaded bool) (V, xsync.ComputeOp) {\n\t\tif wasLoaded {\n\t\t\texisting = oldValue\n\t\t\tloaded = true\n\t\t}\n\t\treturn value, xsync.UpdateOp\n\t})\n\treturn existing, loaded\n}\n\n// LoadOrCompute returns the existing value for the key if present. Otherwise, it computes the value using\n// the provided function and returns the computed value. The loaded result is true if the value was loaded,\n// false if stored.\n//\n// This call locks a hash table bucket while the compute function is executed. It means that modifications\n// on other entries in the bucket will be blocked until the valueFn executes. Consider this when the function\n// includes long-running operations.\nfunc (m *Map[K, V]) LoadOrCompute(key K, valueFn func() V) (actual V, loaded bool) {\n\tactual, _ = m.Compute(key, func(oldValue V, wasLoaded bool) (V, xsync.ComputeOp) {\n\t\tif wasLoaded {\n\t\t\tloaded = true\n\t\t\treturn oldValue, xsync.CancelOp\n\t\t}\n\t\treturn valueFn(), xsync.UpdateOp\n\t})\n\treturn actual, loaded\n}\n\n// LoadOrStore returns the existing value for the key if present. Otherwise, it stores and returns the given value.\n// The loaded result is true if the value was loaded, false if stored.\nfunc (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {\n\tactual, _ = m.Compute(key, func(oldValue V, wasLoaded bool) (V, xsync.ComputeOp) {\n\t\tif wasLoaded {\n\t\t\tloaded = true\n\t\t\treturn oldValue, xsync.CancelOp\n\t\t}\n\t\treturn value, xsync.UpdateOp\n\t})\n\treturn actual, loaded\n}\n\n// Store stores a new value for the key.\nfunc (m *Map[K, V]) Store(key K, value V) {\n\tm.Compute(key, func(oldValue V, wasLoaded bool) (V, xsync.ComputeOp) { return value, xsync.UpdateOp })\n}\n\n// markSubscribers marks all subscribers interested in the given key and value binding.\nfunc (m *Map[K, V]) markSubscribers(key K, value V) {\n\tm.subscribers.Range(func(_ uuid.UUID, sb *subscription[K, V]) bool {\n\t\t// Don't run the filter if the subscriber is marked already.\n\t\tif !sb.mark.Load() && (sb.include == nil || sb.include(key, value)) {\n\t\t\tsb.mark.Store(true)\n\t\t}\n\t\treturn true\n\t})\n}\n\n// notify will send a snapshot to all subscribers that have been marked.\nfunc (m *Map[K, V]) notify() {\n\t// We need to loop until all marked snapshots have been sent, because new marks may be added during sending.\n\tdelta := m.makeDelta()\n\tfor didSend := true; didSend; {\n\t\tdidSend = false\n\t\tm.subscribers.Range(func(_ uuid.UUID, sb *subscription[K, V]) bool {\n\t\t\tif sb.mark.CompareAndSwap(true, false) {\n\t\t\t\tdelta.send(sb)\n\t\t\t\tdidSend = true\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n}\n\ntype allDelta[K comparable, V any] struct {\n\tsnapshot map[K]V\n\tupserts  map[K]V\n\tremovals map[K]V\n}\n\nfunc filteredMap[K comparable, V any](m map[K]V, include func(K, V) bool) map[K]V {\n\tif include == nil {\n\t\treturn m\n\t}\n\tfm := make(map[K]V)\n\tfor k, v := range m {\n\t\tif include(k, v) {\n\t\t\tfm[k] = v\n\t\t}\n\t}\n\treturn fm\n}\n\nfunc (ad *allDelta[K, V]) filteredDelta(initialized bool, include func(K, V) bool) Delta[K, V] {\n\tvar upserts map[K]V\n\tvar removals map[K]V\n\tif initialized {\n\t\tupserts = ad.upserts\n\t\tremovals = ad.removals\n\t} else {\n\t\tupserts = ad.snapshot\n\t\tremovals = nil\n\t}\n\treturn Delta[K, V]{Upserts: filteredMap(upserts, include), Removals: filteredMap(removals, include)}\n}\n\nfunc (ad *allDelta[K, V]) send(sb *subscription[K, V]) {\n\tinitialized := sb.initialized.Swap(true)\n\tfd := ad.filteredDelta(initialized, sb.include)\n\tif initialized && len(fd.Upserts) == 0 && len(fd.Removals) == 0 {\n\t\treturn\n\t}\n\tselect {\n\tcase <-sb.doneCh:\n\tcase prevDelta := <-sb.channel:\n\t\t// The previous delta was not read by the subscriber yet, so we need to merge it with the new delta\n\t\t// and put it back on the channel.\n\t\tprevDelta.Merge(fd)\n\t\tsb.channel <- prevDelta\n\tdefault:\n\t\t// The channel is empty, so we can just send the delta.\n\t\tsb.channel <- fd\n\t}\n}\n\nfunc (m *Map[K, V]) makeDelta() allDelta[K, V] {\n\tm.snapLock.Lock()\n\tprevious := m.snapshot\n\tcurrent := m.LoadAll()\n\tm.snapshot = current\n\tvar upserts map[K]V\n\tfor k, v := range current {\n\t\tif prev, ok := previous[k]; !(ok && m.equal(prev, v)) {\n\t\t\tif upserts == nil {\n\t\t\t\tupserts = make(map[K]V)\n\t\t\t}\n\t\t\tupserts[k] = v\n\t\t}\n\t}\n\tvar removals map[K]V\n\tfor k, v := range previous {\n\t\tif _, ok := current[k]; !ok {\n\t\t\tif removals == nil {\n\t\t\t\tremovals = make(map[K]V)\n\t\t\t}\n\t\t\tremovals[k] = v\n\t\t}\n\t}\n\tm.snapLock.Unlock()\n\treturn allDelta[K, V]{\n\t\tsnapshot: current,\n\t\tupserts:  upserts,\n\t\tremovals: removals,\n\t}\n}\n"
  },
  {
    "path": "pkg/client/agentpf/clients.go",
    "content": "package agentpf\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/agent\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\ttpClient \"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/watcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n)\n\ntype client struct {\n\t// Mutex protects the following fields (the rest is immutable)\n\t//   info.intercepted\n\t//   cli\n\t//   cancelClient\n\t//   cancelDialWatch\n\t// cli and cancelClient are both safe to use without a mutex once the ready channel is closed.\n\t*k8s.Cluster\n\tsync.RWMutex\n\tcli             agent.AgentClient\n\tsession         *manager.SessionInfo\n\tinfo            *manager.AgentPodInfo\n\tremove          func()\n\tcancelClient    context.CancelFunc\n\tcancelDialWatch context.CancelFunc\n\tconnectErr      error\n\ttunnelCount     int32\n\tlastActive      int64\n}\n\nconst dormantLingerTime = 5 * time.Second\n\nfunc (ac *client) String() string {\n\tif ac == nil {\n\t\treturn \"<nil>\"\n\t}\n\tai := ac.info\n\treturn fmt.Sprintf(\"%s(%s), port %d\", ai.PodName, net.IP(ai.PodIp), ai.ApiPort)\n}\n\nfunc (ac *client) Tunnel(ctx context.Context, opts ...grpc.CallOption) (tunnel.Client, error) {\n\tcli, err := ac.ensureConnect(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclog.Tracef(ctx, \"%s(%s) creating Tunnel over gRPC\", ac, net.IP(ac.info.PodIp))\n\ttc, err := cli.Tunnel(ctx, opts...)\n\tif err != nil {\n\t\tclog.Tracef(ctx, \"%s(%s) failed to create Tunnel over gRPC: %v\", ac, net.IP(ac.info.PodIp), err)\n\t\treturn nil, err\n\t}\n\tatomic.AddInt32(&ac.tunnelCount, 1)\n\tclog.Tracef(ctx, \"%s(%s) have %d active tunnels\", ac, net.IP(ac.info.PodIp), atomic.LoadInt32(&ac.tunnelCount))\n\tgo func() {\n\t\t<-ctx.Done()\n\t\ttc := atomic.LoadInt32(&ac.tunnelCount)\n\t\tif tc > 0 && atomic.CompareAndSwapInt32(&ac.tunnelCount, tc, tc-1) {\n\t\t\tclog.Tracef(ctx, \"%s(%s) have %d active tunnels\", ac, net.IP(ac.info.PodIp), tc-1)\n\t\t}\n\t}()\n\tatomic.StoreInt64(&ac.lastActive, time.Now().UnixNano())\n\treturn tc, nil\n}\n\nfunc (ac *client) ensureConnect(ctx context.Context) (agent.AgentClient, error) {\n\tac.Lock()\n\tdefer ac.Unlock()\n\treturn ac.ensureConnectLocked(ctx)\n}\n\nfunc (ac *client) ensureConnectLocked(ctx context.Context) (agent.AgentClient, error) {\n\tif ac.connectErr != nil {\n\t\treturn nil, ac.connectErr\n\t}\n\n\tif ac.cli == nil {\n\t\tdialCtx, dialCancel := context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer dialCancel()\n\n\t\tai := ac.info\n\t\tconn, cli, _, err := ac.ConnectToAgent(dialCtx, ai.PodName, uint16(ai.ApiPort), types.UID(ai.PodId))\n\t\tif err != nil {\n\t\t\tac.connectErr = err\n\n\t\t\t// There's a risk for deadlock here, because of the Range iteration of the map that performs cancel. This cancel will block\n\t\t\t// because we're holding the lock now, and since the Range iteration holds a lock for the entry that we're about to delete,\n\t\t\t// that delete will block. So we let a potential cancel call continue by unlocking before we delete.\n\t\t\tac.Unlock()\n\t\t\tac.remove()\n\t\t\tac.Lock() // Must of course lock again to prevent panic by the pending unlock.\n\t\t\treturn nil, err\n\t\t}\n\n\t\tac.cli = cli\n\t\tac.cancelClient = func() {\n\t\t\t// Need to run this in a separate thread to avoid deadlock.\n\t\t\tgo func() {\n\t\t\t\tconn.Close()\n\t\t\t\tac.Lock()\n\t\t\t\tatomic.StoreInt32(&ac.tunnelCount, 0)\n\t\t\t\tac.cancelClient = nil\n\t\t\t\tac.cli = nil\n\t\t\t\tac.Unlock()\n\t\t\t}()\n\t\t}\n\t}\n\n\tif ac.info.Intercepted {\n\t\terr := ac.startDialWatcherLocked()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tatomic.StoreInt64(&ac.lastActive, time.Now().UnixNano())\n\treturn ac.cli, nil\n}\n\nfunc (ac *client) idleTime() time.Duration {\n\treturn time.Duration(time.Now().UnixNano() - atomic.LoadInt64(&ac.lastActive))\n}\n\nfunc (ac *client) dormant() bool {\n\tif atomic.LoadInt32(&ac.tunnelCount) > 0 || ac.idleTime() < dormantLingerTime {\n\t\treturn false\n\t}\n\tac.RLock()\n\tdormant := ac.cli != nil && !ac.info.Intercepted\n\tac.RUnlock()\n\treturn dormant\n}\n\nfunc (ac *client) connected() bool {\n\tac.RLock()\n\tok := ac.cli != nil\n\tac.RUnlock()\n\treturn ok\n}\n\nfunc (ac *client) intercepted() bool {\n\tac.RLock()\n\tret := ac.info.Intercepted\n\tac.RUnlock()\n\treturn ret\n}\n\nfunc (ac *client) cancel() bool {\n\tac.RLock()\n\tcc := ac.cancelClient\n\tcdw := ac.cancelDialWatch\n\tac.RUnlock()\n\tdidCancel := false\n\tif cdw != nil {\n\t\tdidCancel = true\n\t\tcdw()\n\t}\n\tif cc != nil {\n\t\tdidCancel = true\n\t\tcc()\n\t}\n\treturn didCancel\n}\n\nfunc (ac *client) refresh(ai *manager.AgentPodInfo) {\n\tvar cdw context.CancelFunc\n\tdefer func() {\n\t\tif cdw != nil {\n\t\t\tcdw()\n\t\t}\n\t}()\n\n\tac.Lock()\n\tdefer ac.Unlock()\n\n\toldStatus := ac.info.Intercepted\n\tac.info = ai\n\tif ai.Intercepted == oldStatus {\n\t\treturn\n\t}\n\tif ai.Intercepted {\n\t\tclog.Debugf(ac, \"Agent %s(%s) changed to intercepted\", ai.PodName, net.IP(ai.PodIp))\n\t\tif _, err := ac.ensureConnectLocked(ac); err != nil {\n\t\t\tclog.Errorf(ac, \"failed to start client watcher for %s(%s): %v\", ai.PodName, net.IP(ai.PodIp), err)\n\t\t}\n\t} else {\n\t\t// This agent is no longer intercepting. Stop the dial watcher\n\t\tclog.Debugf(ac, \"Agent %s(%s) changed to not intercepted\", ai.PodName, net.IP(ai.PodIp))\n\t\tcdw = ac.cancelDialWatch\n\t}\n}\n\nfunc (ac *client) startDialWatcherLocked() error {\n\tif ac.cancelDialWatch != nil {\n\t\t// Already started\n\t\treturn nil\n\t}\n\tctx, cancel := context.WithCancel(ac)\n\n\t// Create the dial watcher\n\tclog.Debugf(ctx, \"watching dials from agent pod %s\", ac)\n\tdialStream, err := ac.cli.WatchDial(ctx, ac.session)\n\tif err != nil {\n\t\tcancel()\n\t\treturn err\n\t}\n\n\tac.cancelDialWatch = func() {\n\t\tac.Lock()\n\t\tac.info.Intercepted = false\n\t\tac.cancelDialWatch = nil\n\t\tac.Unlock()\n\t\tcancel()\n\t}\n\n\tgo func() {\n\t\terr := tunnel.DialWaitLoop(ctx, tunnel.AgentToClient, tunnel.AgentProvider(ac.cli), dialStream, tunnel.SessionID(ac.session.SessionId))\n\t\tif err != nil {\n\t\t\t// The traffic-agent closed the dial wait loop, which means that it's terminating.\n\t\t\tclog.Error(ctx, err)\n\t\t}\n\t\tai := ac.info\n\t\tclog.Debugf(ctx, \"DialWaitLoop ended for %s.%s\", ai.PodName, ai.Namespace)\n\t\tac.RLock()\n\t\tdwCancel := ac.cancelDialWatch\n\t\tac.RUnlock()\n\t\tif dwCancel != nil {\n\t\t\tdwCancel()\n\t\t}\n\t}()\n\treturn nil\n}\n\ntype Clients interface {\n\tGetRandomAgent(context.Context) agent.AgentClient\n\tGetClient(netip.Addr) tunnel.Provider\n\tWatchAgentPods(rmc manager.ManagerClient) error\n\tWaitForIP(ctx context.Context, timeout time.Duration, ip netip.Addr) error\n\tWaitForWorkload(timeout time.Duration, name string) error\n\tGetWorkloadClient(workload string) (ag tunnel.Provider)\n\tSetProxyVia(workload string)\n}\n\ntype clients struct {\n\t*k8s.Cluster\n\tsession   *manager.SessionInfo\n\tclients   *xsync.Map[string, *client]\n\tipWaiters *xsync.Map[netip.Addr, chan struct{}]\n\twlWaiters *xsync.Map[string, chan struct{}]\n\tproxyVias *xsync.Map[string, struct{}]\n\tdisabled  atomic.Bool\n}\n\nfunc NewClients(cl *k8s.Cluster, session *manager.SessionInfo) Clients {\n\treturn &clients{\n\t\tCluster:   cl,\n\t\tsession:   session,\n\t\tclients:   xsync.NewMap[string, *client](),\n\t\tipWaiters: xsync.NewMap[netip.Addr, chan struct{}](),\n\t\twlWaiters: xsync.NewMap[string, chan struct{}](),\n\t\tproxyVias: xsync.NewMap[string, struct{}](),\n\t}\n}\n\n// GetClient returns tunnel.Provider that opens a tunnel to a known traffic-agent.\n// The traffic-agent is chosen using the following rules in the order mentioned:\n//\n//  1. agent has a pod_ip that matches the given ip\n//  2. agent is currently intercepted by this client\n//  3. any agent\n//\n// The function returns nil when there are no agents in the connected namespace.\nfunc (s *clients) GetClient(ip netip.Addr) (pvd tunnel.Provider) {\n\tif s.disabled.Load() {\n\t\treturn nil\n\t}\n\tvar primary, secondary, ternary tunnel.Provider\n\ts.clients.Range(func(_ string, c *client) bool {\n\t\tpodIP, ok := netip.AddrFromSlice(c.info.PodIp)\n\t\tswitch {\n\t\tcase ok && ip == podIP:\n\t\t\tprimary = c\n\t\tcase c.intercepted():\n\t\t\tsecondary = c\n\t\tdefault:\n\t\t\tternary = c\n\t\t}\n\t\treturn primary == nil\n\t})\n\tswitch {\n\tcase primary != nil:\n\t\tpvd = primary\n\tcase secondary != nil:\n\t\tpvd = secondary\n\tdefault:\n\t\tpvd = ternary\n\t}\n\treturn pvd\n}\n\n// GetRandomAgent returns an active agent.AgentClient and ensures that it is kept alive\n// for at least 5 seconds.\n//\n// The function returns nil when there are no active agents.\nfunc (s *clients) GetRandomAgent(ctx context.Context) (aa agent.AgentClient) {\n\tvar connected, waiting, other *client\n\ts.clients.Range(func(_ string, ac *client) bool {\n\t\tif ac.connected() {\n\t\t\tconnected = ac\n\t\t\treturn false\n\t\t}\n\t\tif s.isProxyVIA(ac.info) || s.hasWaiterFor(ac.info) {\n\t\t\twaiting = ac\n\t\t} else {\n\t\t\tother = ac\n\t\t}\n\t\treturn true\n\t})\n\n\tvar err error\n\tswitch {\n\tcase connected != nil:\n\t\tconnected.Lock()\n\t\tconnected.lastActive = time.Now().UnixNano()\n\t\taa = connected.cli\n\t\tconnected.Unlock()\n\tcase waiting != nil:\n\t\taa, err = waiting.ensureConnect(ctx)\n\tcase other != nil:\n\t\taa, err = other.ensureConnect(ctx)\n\t}\n\tif err != nil {\n\t\tclog.Warn(s, err)\n\t}\n\treturn aa\n}\n\n// GetWorkloadClient returns tunnel.Provider that opens a tunnel to a traffic-agent that\n// belongs to a pod created for the given workload.\n//\n// The function returns nil when there are no agents for the given workload in the connected namespace.\nfunc (s *clients) GetWorkloadClient(workload string) (pvd tunnel.Provider) {\n\ts.clients.Range(func(_ string, ac *client) bool {\n\t\tif ac.info.WorkloadName == workload {\n\t\t\tpvd = ac\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn pvd\n}\n\nfunc (s *clients) SetProxyVia(workload string) {\n\ts.proxyVias.Store(workload, struct{}{})\n}\n\nfunc (s *clients) isProxyVIA(info *manager.AgentPodInfo) bool {\n\t_, isPV := s.proxyVias.Load(info.WorkloadName)\n\treturn isPV\n}\n\nfunc (s *clients) hasWaiterFor(info *manager.AgentPodInfo) bool {\n\tif podIP, ok := netip.AddrFromSlice(info.PodIp); ok {\n\t\tif _, isW := s.ipWaiters.Load(podIP); isW {\n\t\t\treturn true\n\t\t}\n\t}\n\tif _, isW := s.wlWaiters.Load(info.WorkloadName); isW {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (s *clients) WatchAgentPods(rmc manager.ManagerClient) error {\n\tdefer func() {\n\t\tactiveCount := 0\n\t\ts.clients.Range(func(_ string, ac *client) bool {\n\t\t\tif ac.cancel() {\n\t\t\t\tactiveCount++\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tclog.Debugf(s, \"WatchAgentPods ending with %d clients still active\", activeCount)\n\t\ts.disabled.Store(true)\n\t}()\n\n\tsnapMap := make(map[string]*manager.AgentPodInfo)\n\terr := watcher.WatchWithRetry(s, \"WatchAgentPodsDelta\", tpClient.GetConfig(s).Grpc().WatchRetryInterval,\n\t\tfunc(ctx context.Context) (grpc.ServerStreamingClient[manager.AgentPodInfoDelta], error) {\n\t\t\tclog.Debugf(ctx, \"WatchAgentPodsDelta starting\")\n\t\t\treturn rmc.WatchAgentPodsDelta(ctx, s.session)\n\t\t},\n\t\tfunc(delta *manager.AgentPodInfoDelta) error {\n\t\t\tclog.Debugf(s, \"WatchAgentPodsDelta received %d upserts, %d removals\", len(delta.Upserts), len(delta.Removals))\n\t\t\tmaps.DeltaUpdate(snapMap, delta.Upserts, delta.Removals)\n\t\t\treturn s.updateClients(maps.Values(snapMap))\n\t\t}, func() error {\n\t\t\tclear(snapMap)\n\t\t\treturn nil\n\t\t})\n\tif err == nil || status.Code(err) != codes.Unimplemented {\n\t\treturn err\n\t}\n\n\t// Older traffic-manager. Fall back to watching all agents.\n\tclog.Warnf(s, \"WatchAgentPodsDelta is not implemented by the traffic-manager, falling back to WatchAgentPods and full snapshots\")\n\treturn watcher.WatchWithRetry(s, \"WatchAgentPods\", tpClient.GetConfig(s).Grpc().WatchRetryInterval,\n\t\tfunc(ctx context.Context) (grpc.ServerStreamingClient[manager.AgentPodInfoSnapshot], error) {\n\t\t\tclog.Debugf(ctx, \"No delta support in traffic-manager, starting WatchAgentPods instead\")\n\t\t\treturn rmc.WatchAgentPods(ctx, s.session)\n\t\t},\n\t\tfunc(snapshot *manager.AgentPodInfoSnapshot) error {\n\t\t\treturn s.updateClients(snapshot.Agents)\n\t\t}, nil)\n}\n\nfunc (ac *client) notify(waiter chan struct{}) {\n\t// a client must be connected to be able to notify\n\tif _, err := ac.ensureConnect(ac); err != nil {\n\t\tclog.Errorf(ac, \"notifyWaiters %s (%s), ensureConnect failed: %v\", ac.info.WorkloadName, net.IP(ac.info.PodIp), err)\n\t}\n\tclose(waiter)\n}\n\nfunc (s *clients) notifyWaiters() {\n\ts.clients.Range(func(name string, ac *client) bool {\n\t\tif podIP, ok := netip.AddrFromSlice(ac.info.PodIp); ok {\n\t\t\tif waiter, ok := s.ipWaiters.LoadAndDelete(podIP); ok {\n\t\t\t\tac.notify(waiter)\n\t\t\t}\n\t\t}\n\t\tif waiter, ok := s.wlWaiters.LoadAndDelete(ac.info.WorkloadName); ok {\n\t\t\tac.notify(waiter)\n\t\t}\n\t\treturn true\n\t})\n}\n\nfunc (s *clients) waitWithTimeout(timeout time.Duration, waitOn <-chan struct{}) error {\n\ts.notifyWaiters()\n\tctx, cancel := context.WithTimeout(s, timeout)\n\tdefer cancel()\n\tselect {\n\tcase <-waitOn:\n\t\treturn nil\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\t}\n}\n\nfunc (s *clients) WaitForIP(ctx context.Context, timeout time.Duration, ip netip.Addr) error {\n\tif s.disabled.Load() {\n\t\treturn nil\n\t}\n\tvar cl *client\n\twaitOn, _ := s.ipWaiters.LoadOrCompute(ip, func() (chan struct{}, bool) {\n\t\ts.clients.Range(func(k string, ac *client) bool {\n\t\t\tif podIP, ok := netip.AddrFromSlice(ac.info.PodIp); ok && ip == podIP {\n\t\t\t\tcl = ac\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tif cl != nil {\n\t\t\treturn nil, true\n\t\t}\n\t\treturn make(chan struct{}), false\n\t})\n\tif cl != nil {\n\t\t_, err := cl.ensureConnect(ctx)\n\t\treturn err\n\t}\n\tif err := s.waitWithTimeout(timeout, waitOn); err != nil {\n\t\treturn err\n\t}\n\n\t// Ensure that the client we're waiting for is ready.\n\ts.clients.Range(func(k string, ac *client) bool {\n\t\tif acIP, ok := netip.AddrFromSlice(ac.info.PodIp); ok && ip == acIP {\n\t\t\tcl = ac\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\tif cl == nil {\n\t\treturn status.Error(codes.NotFound, \"no client available\")\n\t}\n\t_, err := cl.ensureConnect(ctx)\n\treturn err\n}\n\nfunc (s *clients) WaitForWorkload(timeout time.Duration, name string) error {\n\tif s.disabled.Load() {\n\t\treturn nil\n\t}\n\n\t// Create a channel to subscribe to, but only if the agent doesn't already exist.\n\twaitOn, ok := s.wlWaiters.LoadOrCompute(name, func() (chan struct{}, bool) {\n\t\tfound := false\n\t\ts.clients.Range(func(k string, ac *client) bool {\n\t\t\tif ac.info.WorkloadName == name {\n\t\t\t\tfound = true\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tif found {\n\t\t\treturn nil, true\n\t\t}\n\t\treturn make(chan struct{}), false\n\t})\n\tif ok {\n\t\treturn s.waitWithTimeout(timeout, waitOn)\n\t}\n\t// No chan created because the agent already exists\n\treturn nil\n}\n\nfunc (s *clients) updateClients(ais []*manager.AgentPodInfo) error {\n\tdefer s.notifyWaiters()\n\n\tvar aim map[string]*manager.AgentPodInfo\n\tif len(ais) > 0 {\n\t\taim = make(map[string]*manager.AgentPodInfo, len(ais))\n\t\tfor _, ai := range ais {\n\t\t\tif ai.PodName != \"\" {\n\t\t\t\taim[ai.PodName+\".\"+ai.Namespace] = ai\n\t\t\t}\n\t\t}\n\t\tif len(aim) == 0 {\n\t\t\t// The current traffic-manager injects old style clients that doesn't report a pod name.\n\t\t\tclog.Debugf(s, \"disabling, because traffic-agent doesn't report pod name\")\n\t\t\ts.disabled.Store(true)\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tdeleteClient := func(k string) {\n\t\ts.clients.Compute(k, func(oldValue *client, loaded bool) (*client, xsync.ComputeOp) {\n\t\t\tif loaded {\n\t\t\t\tclog.Debugf(s, \"Deleting agent %s\", k)\n\t\t\t\toldValue.cancel()\n\t\t\t\treturn nil, xsync.DeleteOp\n\t\t\t}\n\t\t\treturn nil, xsync.CancelOp\n\t\t})\n\t}\n\n\t// Cancel clients that no longer exist.\n\ts.clients.Range(func(k string, _ *client) bool {\n\t\tif _, ok := aim[k]; !ok {\n\t\t\tdeleteClient(k)\n\t\t}\n\t\treturn true\n\t})\n\n\t// Refresh current clients\n\tfor k, ai := range aim {\n\t\tif ac, ok := s.clients.Load(k); ok {\n\t\t\tac.refresh(ai)\n\t\t}\n\t}\n\n\taddClient := func(k string, ai *manager.AgentPodInfo) {\n\t\t_, _ = s.clients.LoadOrCompute(k, func() (*client, bool) {\n\t\t\tac := &client{\n\t\t\t\tCluster: s.Cluster,\n\t\t\t\tsession: s.session,\n\t\t\t\tremove: func() {\n\t\t\t\t\ts.clients.Delete(k)\n\t\t\t\t},\n\t\t\t\tinfo: ai,\n\t\t\t}\n\t\t\tclog.Debugf(s, \"Adding agent pod %s (%s)\", k, net.IP(ai.PodIp))\n\t\t\treturn ac, false\n\t\t})\n\t}\n\n\t// Add clients for newly arrived agents.\n\tfor k, ai := range aim {\n\t\taddClient(k, ai)\n\t}\n\n\t// Terminate all dormant agents except the last one.\n\tdormantCount := 0\n\ts.clients.Range(func(k string, ac *client) bool {\n\t\tif ac.dormant() && !s.isProxyVIA(ac.info) && !s.hasWaiterFor(ac.info) {\n\t\t\tdormantCount++\n\t\t\tif dormantCount > 1 {\n\t\t\t\tclog.Debugf(s, \"Deleting dormant agent %s\", k)\n\t\t\t\tac.cancel()\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\tif dormantCount > 1 {\n\t\tclog.Debugf(s, \"Cancelled %d dormant clients\", dormantCount-1)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/bwcompat/clusterinfo.go",
    "content": "package bwcompat\n\nimport (\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n)\n\nfunc FixLegacyClusterInfo(mgrInfo *manager.ClusterInfo) {\n\t// Older clients use index 1 in the ClusterInfo to pass the kube-dns IP. It\n\t// will manifest itself as one entry of len 4 in the servicecidrs.\n\tif len(mgrInfo.ServiceCidrs) == 1 && len(mgrInfo.ServiceCidrs[0]) == 4 {\n\t\t// Older client with no support for multiple service subnets\n\t\tif mgrInfo.ServiceSubnet != nil {\n\t\t\tcidr := iputil.RPCToPrefix(mgrInfo.ServiceSubnet)\n\t\t\tsb, _ := cidr.MarshalBinary()\n\t\t\tmgrInfo.ServiceCidrs = [][]byte{sb}\n\t\t} else {\n\t\t\tmgrInfo.ServiceCidrs = nil\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cache/cache.go",
    "content": "package cache\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\ntype Permissions fs.FileMode\n\nconst (\n\tPublic  Permissions = 0o644\n\tPrivate Permissions = 0o600\n)\n\nfunc SaveToUserCache(ctx context.Context, object any, file string, perm Permissions) error {\n\tctx = dos.WithLockedFs(ctx)\n\tjsonContent, err := json.Marshal(object)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// add file path (ex. \"ispec/00-00-0000.json\")\n\tfullFilePath := filepath.Join(filelocation.AppUserCacheDir(ctx), file)\n\t// get dir of joined path\n\tdir := filepath.Dir(fullFilePath)\n\tif err := dos.MkdirAll(ctx, dir, 0o755); err != nil {\n\t\treturn err\n\t}\n\treturn dos.WriteFile(ctx, fullFilePath, jsonContent, (fs.FileMode(perm)))\n}\n\nfunc LoadFromUserCache(ctx context.Context, dest any, file string) error {\n\tctx = dos.WithLockedFs(ctx)\n\tpath := filepath.Join(filelocation.AppUserCacheDir(ctx), file)\n\tjsonContent, err := dos.ReadFile(ctx, path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := json.Unmarshal(jsonContent, &dest); err != nil {\n\t\treturn fmt.Errorf(\"failed to parse JSON from file %s: %w\", path, err)\n\t}\n\treturn nil\n}\n\nfunc DeleteFromUserCache(ctx context.Context, file string) error {\n\tctx = dos.WithLockedFs(ctx)\n\tif err := dos.Remove(ctx, filepath.Join(filelocation.AppUserCacheDir(ctx), file)); err != nil && !errors.Is(err, fs.ErrNotExist) {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc TouchUserCache(ctx context.Context, file string) error {\n\tnow := time.Now()\n\treturn os.Chtimes(filepath.Join(filelocation.AppUserCacheDir(ctx), file), now, now)\n}\n\nfunc UserCacheModTime(ctx context.Context, file string) (time.Time, error) {\n\tst, err := os.Stat(filepath.Join(filelocation.AppUserCacheDir(ctx), file))\n\tif err != nil {\n\t\treturn time.Time{}, err\n\t}\n\treturn st.ModTime(), nil\n}\n\nfunc ExistsInCache(ctx context.Context, fileName string) (bool, error) {\n\tctx = dos.WithLockedFs(ctx)\n\tpath := filepath.Join(filelocation.AppUserCacheDir(ctx), fileName)\n\tif _, err := dos.Stat(ctx, path); err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\treturn false, nil\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/client/cache/watcher.go",
    "content": "package cache\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\n// WatchUserCache uses a file system watcher that receives events when one of the given files changes\n// and calls the given function when that happens.\n// All files in the given subDir are watched when the list of files is empty.\nfunc WatchUserCache(ctx context.Context, subDir string, onChange func(context.Context) error, files ...string) error {\n\tdir := filepath.Join(filelocation.AppUserCacheDir(ctx), subDir)\n\n\t// Ensure that the user cache directory exists.\n\tif err := dos.MkdirAll(ctx, dir, 0o755); err != nil {\n\t\treturn err\n\t}\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer watcher.Close()\n\n\t// The directory containing the files must be watched because editing a\n\t// file will typically end with renaming the original and then creating\n\t// a new file. A watcher that follows the inode will not see when the new\n\t// file is created.\n\tif err = watcher.Add(dir); err != nil {\n\t\treturn err\n\t}\n\n\t// The delay timer will initially sleep forever. It's reset to a very short\n\t// delay when the file is modified.\n\tdelay := time.AfterFunc(time.Duration(math.MaxInt64), func() {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\tif err := onChange(ctx); err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t}\n\t})\n\tdefer delay.Stop()\n\n\tisOfInterest := func(string) bool { return true }\n\tif len(files) > 0 {\n\t\tfor i := range files {\n\t\t\tfiles[i] = filepath.Join(dir, files[i])\n\t\t}\n\t\tisOfInterest = func(s string) bool {\n\t\t\tfor _, file := range files {\n\t\t\t\tif s == file {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t}\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tcase err = <-watcher.Errors:\n\t\t\tclog.Error(ctx, err)\n\t\tcase event := <-watcher.Events:\n\t\t\tif event.Op&(fsnotify.Remove|fsnotify.Write|fsnotify.Create) != 0 && isOfInterest(event.Name) {\n\t\t\t\t// The file was created, modified, or removed. Let's defer the call to onChange just\n\t\t\t\t// a little bit in case there are more modifications to it.\n\t\t\t\tdelay.Reset(5 * time.Millisecond)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/ann/annotations.go",
    "content": "package ann\n\n// -- Annotation keys\n\nconst (\n\tUserDaemon        = \"userD\"\n\tSession           = \"session\"\n\tVersionCheck      = \"versionCheck\"\n\tUpdateCheckFormat = \"updateCheckFormat\"\n\tProgress          = \"progress\"\n)\n\n// -- Annotation values\n\nconst (\n\tOptional = \"optional\"\n\tRequired = \"required\"\n\tTel2     = \"https://%s/download/tel2/%s/%s/stable.txt\"\n)\n"
  },
  {
    "path": "pkg/client/cli/cmd/completion.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n)\n\nconst completionLongPlain = `To load completions:\n\nBash:\n\n  $ source <(%[1]s completion bash)\n\n  # To load completions for each session, execute once:\n  # Linux:\n  $ %[1]s completion bash > /etc/bash_completion.d/%[1]s\n  # macOS:\n  $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s\n\nZsh:\n\n  # If shell completion is not already enabled in your environment,\n  # you will need to enable it.  You can execute the following once:\n\n  $ echo \"autoload -U compinit; compinit\" >> ~/.zshrc\n\n  # To load completions for each session, execute once:\n  $ %[1]s completion zsh > \"${fpath[1]}/_%[1]s\"\n\n  # You will need to start a new shell for this setup to take effect.\n\nfish:\n\n  $ %[1]s completion fish | source\n\n  # To load completions for each session, execute once:\n  $ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish\n\nPowerShell:\n\n  PS> %[1]s completion powershell | Out-String | Invoke-Expression\n\n  # To load completions for every new session, run:\n  PS> %[1]s completion powershell > %[1]s.ps1\n  # and source this file from your PowerShell profile.\n`\n\nconst completionLongMarkdown = `To load completions:\n\n### Bash:\n` + \"```bash\" + `\n  $ source <(%[1]s completion bash)\n\n  # To load completions for each session, execute once:\n  # Linux:\n  $ %[1]s completion bash > /etc/bash_completion.d/%[1]s\n  # macOS:\n  $ %[1]s completion bash > $(brew --prefix)/etc/bash_completion.d/%[1]s\n` + \"```\" + `\n\n### Zsh:\n` + \"```zsh\" + `\n\n  # If shell completion is not already enabled in your environment,\n  # you will need to enable it.  You can execute the following once:\n\n  $ echo \"autoload -U compinit; compinit\" >> ~/.zshrc\n\n  # To load completions for each session, execute once:\n  $ %[1]s completion zsh > \"${fpath[1]}/_%[1]s\"\n\n  # You will need to start a new shell for this setup to take effect.\n` + \"```\" + `\n\n### fish:\n` + \"```fish\" + `\n\n  $ %[1]s completion fish | source\n\n  # To load completions for each session, execute once:\n  $ %[1]s completion fish > ~/.config/fish/completions/%[1]s.fish\n` + \"```\" + `\n\n### PowerShell:\n` + \"```powershell\" + `\n\n  PS> %[1]s completion powershell | Out-String | Invoke-Expression\n\n  # To load completions for every new session, run:\n  PS> %[1]s completion powershell > %[1]s.ps1\n  # and source this file from your PowerShell profile.\n` + \"```\"\n\nfunc addCompletion(rootCmd *cobra.Command, markdown bool) {\n\tlongText := completionLongPlain\n\tif markdown {\n\t\tlongText = completionLongMarkdown\n\t}\n\tcmd := cobra.Command{\n\t\tUse:   \"completion\",\n\t\tShort: \"Generate a shell completion script\",\n\t\tValidArgs: []string{\n\t\t\t\"bash\",\n\t\t\t\"zsh\",\n\t\t\t\"powershell\",\n\t\t\t\"fish\",\n\t\t},\n\t\tArgAliases: []string{\"ps\"},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tvar shell string\n\t\t\tif 0 < len(args) {\n\t\t\t\tshell = args[0]\n\t\t\t}\n\n\t\t\tvar err error\n\t\t\tswitch shell {\n\t\t\tcase \"zsh\":\n\t\t\t\terr = rootCmd.GenZshCompletionNoDesc(os.Stdout)\n\t\t\tcase \"bash\":\n\t\t\t\terr = rootCmd.GenBashCompletionV2(os.Stdout, false)\n\t\t\tcase \"fish\":\n\t\t\t\terr = rootCmd.GenFishCompletion(os.Stdout, false)\n\t\t\tcase \"ps\", \"powershell\":\n\t\t\t\terr = rootCmd.GenPowerShellCompletion(os.Stdout)\n\t\t\tcase \"\":\n\t\t\t\terr = errcat.User.Newf(\"shell not specified\")\n\t\t\t}\n\n\t\t\treturn err\n\t\t},\n\t\tLong: fmt.Sprintf(longText, rootCmd.Name()),\n\t}\n\tcmd.SetContext(rootCmd.Context())\n\trootCmd.AddCommand(&cmd)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/compose.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/docker/compose\"\n)\n\nfunc composeCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:              \"compose command [flags]\",\n\t\tShort:            \"Define and run multi-container applications with Telepresence and Docker\",\n\t\tTraverseChildren: true,\n\t}\n\tcmd.AddCommand(compose.GenerateSubCommands(cmd)...)\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/config.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n)\n\nfunc configCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"config\",\n\t\tShort: \"Telepresence configuration commands\",\n\t}\n\tcmd.AddCommand(configView())\n\treturn cmd\n}\n\nconst clientOnlyFlag = \"client-only\"\n\nfunc configView() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"view\",\n\t\tArgs:              cobra.NoArgs,\n\t\tPersistentPreRunE: output.DefaultYAML,\n\t\tShort:             \"View current Telepresence configuration\",\n\t\tRunE:              runConfigView,\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Optional,\n\t\t},\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\tcmd.Flags().BoolP(clientOnlyFlag, \"c\", false, \"Only view config from client file.\")\n\treturn cmd\n}\n\nfunc runConfigView(cmd *cobra.Command, _ []string) error {\n\tdefer func() {\n\t\tprogress.Stop(cmd.Context())\n\t}()\n\n\tvar cfg client.SessionConfig\n\tclientOnly, _ := cmd.Flags().GetBool(clientOnlyFlag)\n\tif !clientOnly {\n\t\tcmd.Annotations = map[string]string{\n\t\t\tann.Session: ann.Required,\n\t\t}\n\t\tif err := connect.InitCommand(cmd); err != nil {\n\t\t\t// Unable to establish a session, so try to convey the local config instead. It\n\t\t\t// may be helpful in diagnosing the problem.\n\t\t\tcmd.Annotations = map[string]string{}\n\t\t\tclientOnly = true\n\t\t}\n\t}\n\n\tif clientOnly {\n\t\tif err := connect.InitCommand(cmd); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tkc, err := daemon.GetCommandKubeConfig(cmd)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcfg.Config = client.GetConfig(kc)\n\t\tcfg.ClientFile = client.GetConfigFile(kc)\n\t\tcfg.LogDirectory = filelocation.AppUserLogDir(kc)\n\t\toutput.Object(cmd.Context(), &cfg, true)\n\t\treturn nil\n\t}\n\n\tcmd.Annotations = map[string]string{\n\t\tann.Session: ann.Required,\n\t}\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\treturn err\n\t}\n\tctx := cmd.Context()\n\tcc, err := daemon.MustGetUserClient(ctx).GetConfig(ctx, &empty.Empty{})\n\tif err != nil {\n\t\treturn grpc.FromGRPC(err)\n\t}\n\terr = json.Unmarshal(cc.Json, &cfg, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\toutput.Object(ctx, &cfg, true)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/connect.go",
    "content": "package cmd\n\nimport (\n\t\"os\"\n\t\"slices\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n)\n\nfunc connectCmd() *cobra.Command {\n\tvar request *daemon.CobraRequest\n\n\tcmd := &cobra.Command{\n\t\tUse:   \"connect [flags] [-- <command to run while connected>]\",\n\t\tArgs:  cobra.ArbitraryArgs,\n\t\tShort: \"Connect to a cluster\",\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Required,\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif err := request.CommitFlags(cmd); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn connect.RunConnect(cmd, args)\n\t\t},\n\t\tValidArgsFunction: func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {\n\t\t\tdir := cobra.ShellCompDirectiveNoFileComp\n\t\t\tif slices.Contains(os.Args, \"--\") {\n\t\t\t\tdir = cobra.ShellCompDirectiveDefault\n\t\t\t}\n\t\t\treturn nil, dir\n\t\t},\n\t}\n\trequest = daemon.InitRequest(cmd)\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/curl.go",
    "content": "package cmd\n\nimport (\n\t\"slices\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n)\n\nfunc curlCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"curl\",\n\t\tShort: \"curl with daemon network\",\n\t\tArgs:  cobra.ArbitraryArgs,\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Optional,\n\t\t},\n\t\tRunE:                  runCurl,\n\t\tValidArgsFunction:     cobra.NoFileCompletions,\n\t\tSilenceErrors:         true,\n\t\tSilenceUsage:          true,\n\t\tDisableFlagParsing:    true,\n\t\tDisableFlagsInUseLine: true,\n\t\tDisableSuggestions:    true,\n\t}\n\treturn cmd\n}\n\nfunc runCurl(cmd *cobra.Command, args []string) error {\n\tglobal.SetProgressQuiet(cmd)\n\treturn runDockerRun(cmd, slices.Insert(args, 0, \"--rm\", \"curlimages/curl\"))\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/docker_run.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\tcliDocker \"github.com/telepresenceio/telepresence/v2/pkg/client/cli/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nfunc dockerRunCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"docker-run\",\n\t\tShort: \"Docker run with daemon network\",\n\t\tArgs:  cobra.ArbitraryArgs,\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Optional,\n\t\t},\n\t\tRunE:                  runDockerRunCLI,\n\t\tSilenceErrors:         true,\n\t\tSilenceUsage:          true,\n\t\tDisableFlagParsing:    true,\n\t\tDisableFlagsInUseLine: true,\n\t\tValidArgsFunction:     cliDocker.AutocompleteRun,\n\t}\n\treturn cmd\n}\n\nfunc findAndParseFlag(flags *pflag.FlagSet, flagName string, args []string) ([]string, error) {\n\tif i := slices.Index(args, \"--\"+flagName); i >= 0 && i+1 < len(args) {\n\t\tif err := flags.Parse(args[i : i+2]); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\targs = slices.Delete(args, i, i+2)\n\t} else if i = slices.IndexFunc(args, func(s string) bool { return strings.HasPrefix(s, \"--\"+flagName+\"=\") }); i >= 0 {\n\t\tif err := flags.Parse(args[i : i+1]); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\targs = slices.Delete(args, i, i+1)\n\t}\n\treturn args, nil\n}\n\nfunc parseFlags(cmd *cobra.Command, args []string) ([]string, error) {\n\t// The command has all flag parsing disabled, but we must check for the global flags. Luckily, these flags do not conflict with\n\t// the docker run flags.\n\topts := cmd.Flags()\n\tvar err error\n\tfor _, n := range global.FlagNames {\n\t\targs, err = findAndParseFlag(opts, n, args)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn args, nil\n}\n\nfunc runDockerRunCLI(cmd *cobra.Command, args []string) error {\n\treturn errcat.NoDaemonLogs.New(runDockerRun(cmd, args))\n}\n\nfunc runDockerRun(cmd *cobra.Command, args []string) error {\n\targs, err := parseFlags(cmd, args)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif slices.Contains(args, \"--help\") {\n\t\treturn proc.StdCommand(cmd.Context(), docker.Exe, slices.Insert(args, 0, \"run\")...).Run()\n\t}\n\n\terr = connect.InitCommand(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tctx, cancel := context.WithCancel(cmd.Context())\n\tdefer cancel()\n\n\tud := daemon.GetUserClient(ctx)\n\tif ud == nil {\n\t\treturn fmt.Errorf(\"%s requires a connection\", cmd.UseLine())\n\t}\n\tif !ud.Containerized() {\n\t\treturn fmt.Errorf(\"%s requires that --docker was used when the connection was established\", cmd.UseLine())\n\t}\n\tif name, ok, err := flags.GetUnparsedValue(\"name\", 0, false, args); err == nil && ok {\n\t\tip, err := ud.Lookup(ctx, name)\n\t\tif err == nil {\n\t\t\t// We're about to start a container with a name that is already present in the cluster. That's\n\t\t\t// probably a mistake.\n\t\t\tfmt.Fprintf(cmd.ErrOrStderr(), \"Warning! The container name %q will override the current mapping to IP %s\\n\", name, ip)\n\t\t}\n\t}\n\n\tcni, cc, err := docker.Start(ctx, true, args...)\n\tif err != nil {\n\t\treturn errcat.NoDaemonLogs.New(err)\n\t}\n\tif cc == nil {\n\t\t// Container already exited\n\t\treturn nil\n\t}\n\n\tvar exited, signalled atomic.Bool\n\tdone := make(chan error, 1)\n\tif flags.HasOption(\"tty\", 't', args) {\n\t\tclose(done)\n\t} else {\n\t\tgo cliDocker.EnsureStopContainer(ctx, cni.Name, cni.ID, nil, &exited, &signalled, done)\n\t}\n\n\terr = cc.Wait()\n\texited.Store(true)\n\tcancel()\n\tif signalled.Load() {\n\t\terr = nil\n\t}\n\twaitErr := <-done\n\tif err == nil {\n\t\terr = waitErr\n\t}\n\treturn errcat.NoDaemonLogs.New(err)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/gather_logs.go",
    "content": "package cmd\n\nimport (\n\t\"archive/zip\"\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype gatherLogsCommand struct {\n\toutputFile     string\n\tdaemons        string\n\ttrafficAgents  string\n\ttrafficManager bool\n\tanon           bool\n\tpodYaml        bool\n}\n\nfunc gatherLogs() *cobra.Command {\n\tgl := &gatherLogsCommand{}\n\tcmd := &cobra.Command{\n\t\tUse:   \"gather-logs\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Gather logs from traffic-manager, traffic-agent, user and root daemons, and export them into a zip file.\",\n\t\tLong: `Gather logs from traffic-manager, traffic-agent, user and root daemons,\nand export them into a zip file. Useful if you are opening a Github issue or asking\nsomeone to help you debug Telepresence.`,\n\t\tExample: `Here are a few examples of how you can use this command:\n# Get all logs and export to a given file\ntelepresence gather-logs -o /tmp/telepresence_logs.zip\n\n# Get all logs and pod yaml manifests for components in the kubernetes cluster\ntelepresence gather-logs -o /tmp/telepresence_logs.zip --get-pod-yaml\n\n# Get all logs for the daemons only\ntelepresence gather-logs --traffic-agents=None --traffic-manager=False\n\n# Get all logs for pods that have \"echo-easy\" in the name, useful if you have multiple replicas\ntelepresence gather-logs --traffic-manager=False --traffic-agents=echo-easy\n\n# Get all logs for a specific pod\ntelepresence gather-logs --traffic-manager=False --traffic-agents=echo-easy-6848967857-tw4jw\n\n# Get logs from everything except the daemons\ntelepresence gather-logs --daemons=None\n`,\n\n\t\tRunE: gl.gatherLogs,\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Optional,\n\t\t},\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\tflags := cmd.Flags()\n\tflags.StringVarP(&gl.outputFile, \"output-file\", \"o\", \"\", \"The file you want to output the logs to.\")\n\tflags.StringVar(&gl.daemons, \"daemons\", \"all\", \"Comma separated list of daemons you want logs from: all, root, user, kubeauth, None\")\n\tflags.BoolVar(&gl.trafficManager, \"traffic-manager\", true, \"If you want to collect logs from the traffic-manager\")\n\tflags.StringVar(&gl.trafficAgents, \"traffic-agents\", \"all\", \"Traffic-agents to collect logs from: all, name substring, None\")\n\tflags.BoolVarP(&gl.anon, \"anonymize\", \"a\", false, \"To anonymize pod names + namespaces from the logs\")\n\tflags.BoolVarP(&gl.podYaml, \"get-pod-yaml\", \"y\", false, \"Get the yaml of any pods you are getting logs for\")\n\treturn cmd\n}\n\n// anonymizer contains the mappings between things we want to anonymize\n// and their new, anonymized name.  Using a map instead of simply redacting\n// makes it easier for us to maintain certain relationships in the logs (e.g.\n// namespaces things are in) which may be helpful in troubleshooting.\ntype anonymizer struct {\n\tnamespaces map[string]string\n\tpodNames   map[string]string\n}\n\n// gatherLogs gets the logs from the daemons (daemon + connector) and creates a zip.\nfunc (gl *gatherLogsCommand) gatherLogs(cmd *cobra.Command, _ []string) error {\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\treturn err\n\t}\n\tctx := cmd.Context()\n\tdefer progress.Stop(ctx)\n\n\tctx = dos.WithStdio(ctx, cmd)\n\n\t// If the user did not provide an outputFile, we'll use their current working directory\n\tif gl.outputFile == \"\" {\n\t\tpwd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\treturn errcat.User.New(err)\n\t\t}\n\t\tgl.outputFile = filepath.Join(pwd, fmt.Sprintf(\"telepresence_logs_%s.zip\", time.Now().UTC().Format(\"060102T150405\")))\n\t} else if !strings.HasSuffix(gl.outputFile, \".zip\") {\n\t\treturn errcat.User.New(\"output file must end in .zip\")\n\t}\n\n\t// We will store the logs in a temp dir in the users log directory before we zip them for export, so that\n\t// containerized daemons will use the same place.\n\texportDir, err := os.MkdirTemp(filelocation.AppUserCacheDir(ctx), \"logs-*\")\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\tdefer func() {\n\t\tif err := os.RemoveAll(exportDir); err != nil {\n\t\t\tioutil.Printf(dos.Stderr(ctx), \"Failed to remove temp directory %s: %s\", exportDir, err)\n\t\t}\n\t}()\n\n\t// Add the daemonLogs to the export directory\n\tvar daemonLogs []string\n\tfor _, daemon := range strings.Split(gl.daemons, \",\") {\n\t\tdaemon = strings.TrimSpace(daemon)\n\t\tswitch daemon {\n\t\tcase \"all\":\n\t\t\tdaemonLogs = append(daemonLogs, \"cli\", \"connector\", \"daemon\", \"kubeauth\")\n\t\tcase \"cli\":\n\t\t\tdaemonLogs = append(daemonLogs, \"cli\")\n\t\tcase \"daemon\", \"root\":\n\t\t\tdaemonLogs = append(daemonLogs, \"daemon\")\n\t\tcase \"connector\", \"user\":\n\t\t\tdaemonLogs = append(daemonLogs, \"connector\")\n\t\tcase \"kubeauth\":\n\t\t\tdaemonLogs = append(daemonLogs, \"kubeauth\")\n\t\tcase \"\", \"None\":\n\t\tdefault:\n\t\t\treturn errcat.User.New(\"Options for --daemons are: all, root, user, or None\")\n\t\t}\n\t}\n\n\tvar az *anonymizer\n\tif gl.anon {\n\t\taz = &anonymizer{\n\t\t\tnamespaces: make(map[string]string),\n\t\t\tpodNames:   make(map[string]string),\n\t\t}\n\t}\n\n\t// Since getting the logs from k8s requires the connector, let's only do this\n\t// work if we know the user wants to get logs from k8s.\n\t// We gather those logs before we gather the connector.log so that problems that\n\t// may occur during that process will be included in the connector.log\n\tif gl.trafficManager || gl.trafficAgents != \"None\" {\n\t\tif err := gl.gatherClusterLogs(ctx, exportDir, az); err != nil {\n\t\t\t// We let the user know we were unable to get logs from the kubernetes components,\n\t\t\t// and why, but this shouldn't block the command returning successful with the logs\n\t\t\t// it was able to get.\n\t\t\tioutil.Printf(dos.Stderr(ctx), \"error getting logs from kubernetes components: %s\\n\", err)\n\t\t}\n\t}\n\n\terr = retrieveLocalLogs(ctx, daemonLogs, exportDir)\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\n\t// Zip up all the files we've created in the zip directory and return that to the user\n\tfiles, err := getLogFiles(exportDir)\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\tif az != nil {\n\t\tanonymizeLogs(ctx, files, az)\n\t}\n\tif err = zipFiles(files, gl.outputFile); err != nil {\n\t\treturn err\n\t}\n\n\tioutil.Printf(dos.Stdout(ctx), \"Logs have been exported to %s\\n\", gl.outputFile)\n\treturn nil\n}\n\n// retrieveLocalLogs retrieves all logs from the logDir that match the daemons the user cares about.\nfunc retrieveLocalLogs(ctx context.Context, daemonLogs []string, exportDir string) error {\n\t// Get all logs from the logDir that match the daemons the user cares about.\n\tlogDir := filelocation.AppUserLogDir(ctx)\n\tlogFiles, err := os.ReadDir(logDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, entry := range logFiles {\n\t\tif entry.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, logType := range daemonLogs {\n\t\t\tif !strings.Contains(entry.Name(), logType) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsrcFile := filepath.Join(logDir, entry.Name())\n\n\t\t\t// The cli.log is often empty, so this check is relevant.\n\t\t\tempty, err := isEmpty(srcFile)\n\t\t\tif err != nil {\n\t\t\t\tioutil.Printf(dos.Stderr(ctx), \"failed stat on %s: %s\\n\", entry.Name(), err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif empty {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdstFile := filepath.Join(exportDir, entry.Name())\n\t\t\tif err := copyFiles(dstFile, srcFile); err != nil {\n\t\t\t\t// We don't want to fail / exit abruptly if we can't copy certain\n\t\t\t\t// files, but we do want the user to know we were unsuccessful\n\t\t\t\tioutil.Printf(dos.Stderr(ctx), \"failed exporting %s: %s\\n\", entry.Name(), err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getLogFiles(exportDir string) ([]string, error) {\n\tdirEntries, err := os.ReadDir(exportDir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfiles := make([]string, 0, len(dirEntries))\n\tfor _, entry := range dirEntries {\n\t\tif !entry.IsDir() {\n\t\t\tfiles = append(files, filepath.Join(exportDir, entry.Name()))\n\t\t}\n\t}\n\treturn files, nil\n}\n\nfunc anonymizeLogs(ctx context.Context, files []string, az *anonymizer) {\n\tfor _, fullFileName := range files {\n\t\tif err := az.anonymizeLog(fullFileName); err != nil {\n\t\t\tioutil.Printf(dos.Stderr(ctx), \"error anonymizing %s: %s\\n\", fullFileName, err)\n\t\t}\n\t\tfiles = append(files, fullFileName)\n\t}\n}\n\nfunc (gl *gatherLogsCommand) gatherClusterLogs(ctx context.Context, exportDir string, az *anonymizer) error {\n\t// To get logs from the components in the kubernetes cluster, we ask the\n\t// traffic-manager.\n\trq := &connector.LogsRequest{\n\t\tTrafficManager: gl.trafficManager,\n\t\tAgents:         gl.trafficAgents,\n\t\tGetPodYaml:     gl.podYaml,\n\t\tExportDir:      filepath.Base(exportDir),\n\t}\n\tuserD := daemon.GetUserClient(ctx)\n\tif userD != nil {\n\t\tvar opts []grpc.CallOption\n\t\tcfg := client.GetConfig(ctx)\n\t\tif mz := cfg.Grpc().MaxReceiveSize(); mz > 0 {\n\t\t\topts = append(opts, grpc.MaxCallRecvMsgSize(int(mz)))\n\t\t}\n\t\tlr, err := userD.GatherLogs(ctx, rq, opts...)\n\t\tif err != nil {\n\t\t\treturn tpGrpc.FromGRPC(err)\n\t\t}\n\t\tif az != nil {\n\t\t\tif err := az.anonymizeFileNames(lr, exportDir); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc isEmpty(file string) (bool, error) {\n\ts, err := os.Stat(file)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn s.Size() == 0, err\n}\n\n// copyFiles copies files from one location into another.\nfunc copyFiles(dstFile, srcFile string) error {\n\tsrcWriter, err := os.Open(srcFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer srcWriter.Close()\n\n\tdstWriter, err := os.Create(dstFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer dstWriter.Close()\n\n\tif _, err := io.Copy(dstWriter, srcWriter); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// zipFiles creates a zip file with the contents of all the files passed in.\n// If some files do not exist, it will include that in the error message,\n// but it will still create a zip file with as many files as it can.\nfunc zipFiles(files []string, zipFileName string) error {\n\tzipFile, err := os.Create(zipFileName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer zipFile.Close()\n\n\tzipWriter := zip.NewWriter(zipFile)\n\tdefer zipWriter.Close()\n\n\taddFileToZip := func(file string) error {\n\t\tfd, err := os.Open(file)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer fd.Close()\n\n\t\t// Get the header information from the original file\n\t\tfileInfo, err := os.Stat(file)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfileHeader, err := zip.FileInfoHeader(fileInfo)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfileHeader.Method = zip.Deflate\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Get the basename of the file since that's all we want\n\t\t// to include in the zip\n\t\tbaseName := filepath.Base(file)\n\n\t\tfileHeader.Name = baseName\n\t\tzfd, err := zipWriter.CreateHeader(fileHeader)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err := io.Copy(zfd, fd); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Make a note of the files we fail to add to the zip so users know if the\n\t// zip is incomplete\n\terrMsg := \"\"\n\tfor _, file := range files {\n\t\t// If the file doesn't have a name, then we obviously can't add it to\n\t\t// the zip. We have handling elsewhere to prevent files like this from\n\t\t// getting here but are extra cautious.\n\t\tif file == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tif err := addFileToZip(file); err != nil {\n\t\t\terrMsg += fmt.Sprintf(\"failed adding %s to zip file: %s \", file, err)\n\t\t}\n\t}\n\tif errMsg != \"\" {\n\t\treturn errors.New(errMsg)\n\t}\n\treturn nil\n}\n\n// anonymizeFileNames will anonymize the file names of all pods in the connector.LogResponse.\nfunc (a *anonymizer) anonymizeFileNames(lr *connector.LogsResponse, exportDir string) error {\n\tfor n, v := range lr.PodInfo {\n\t\tqn := filepath.Join(exportDir, n)\n\t\tif v != \"ok\" {\n\t\t\t// Write the error to retrieve the log as the log content. It's better than nothing\n\t\t\t_ = os.WriteFile(qn, []byte(v), 0o666)\n\t\t}\n\t\tanonQn := filepath.Join(exportDir, a.getPodName(n))\n\t\tif err := os.Rename(qn, anonQn); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to anonymize by renaming file name %s to %s\", qn, anonQn)\n\t\t}\n\t}\n\treturn nil\n}\n\n// getPodName returns an anonymized version of the podName. The anonymized value is cached so that\n// the same anonymized name will be returned on subsequent calls using the same podName.\nfunc (a *anonymizer) getPodName(podName string) string {\n\t// If this pod name has already been mapped, return that\n\tif anonName, ok := a.podNames[podName]; ok {\n\t\treturn anonName\n\t}\n\n\t// the podName hasn't been anonymized yet so we split it up\n\t// so we can anonymize the namespace\n\tnameComponents := strings.SplitN(podName, \".\", 2)\n\tif len(nameComponents) != 2 {\n\t\t// Note: the ordinal here is based on the total number of\n\t\t// pods, not the number of anonPods that are found. This\n\t\t// shouldn't be a problem because the main goal of this\n\t\t// is to make them distinct, but should we ever want the\n\t\t// ordinals to be strictly for anonPods, we'll need to\n\t\t// make a change here.\n\t\tunknownPodName := fmt.Sprintf(\"anonPod-%d.anonNamespace\",\n\t\t\tlen(a.podNames)+1)\n\t\ta.podNames[podName] = unknownPodName\n\t\treturn unknownPodName\n\t}\n\tvar anonPodName, anonNamespace string\n\tname, namespace := nameComponents[0], nameComponents[1]\n\tif val, ok := a.namespaces[namespace]; ok {\n\t\tanonNamespace = val\n\t} else {\n\t\tanonNamespace = fmt.Sprintf(\"namespace-%d\", len(a.namespaces)+1)\n\t\ta.namespaces[namespace] = anonNamespace\n\t}\n\n\t// we want to special case the traffic-manager so we can easily distinguish\n\t// between that and the traffic-agents\n\tif strings.Contains(name, agentconfig.ManagerAppName) {\n\t\tanonPodName = fmt.Sprintf(\"%s.%s\", agentconfig.ManagerAppName, anonNamespace)\n\t} else {\n\t\tanonPodName = fmt.Sprintf(\"pod-%d.%s\", len(a.podNames)+1, anonNamespace)\n\t}\n\t// Store the anonPodName in the map\n\ta.podNames[podName] = anonPodName\n\treturn anonPodName\n}\n\n// anonymizeLog is a helper function that replaces the namespace + podName\n// used in the log with its anonymized version, provided by the anonymizer.\n// It overwrites the file with the anonymized version.\nfunc (a *anonymizer) anonymizeLog(logFile string) error {\n\t// Read the contents we are going to overwrite from the file\n\tcontent, err := os.ReadFile(logFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Open the file with write so we can overwrite it\n\tstringContent := string(content)\n\tf, err := os.OpenFile(logFile, os.O_RDWR, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\t// First we replace the actual namespace with the anonymized\n\t// version.\n\tfor namespace, anonNamespace := range a.namespaces {\n\t\tstringContent = strings.ReplaceAll(stringContent, namespace, anonNamespace)\n\t}\n\t// Now we do pod name which is a little bit more complicated\n\tfor fullPodName, fullAnonPodName := range a.podNames {\n\t\t// strip the namespace off of the anonymized name\n\t\tanonPodParts := strings.Split(fullAnonPodName, \".\")\n\t\tanonPodName := anonPodParts[0]\n\n\t\t// Strip the namespace off of the podName\n\t\tpodParts := strings.Split(fullPodName, \".\")\n\n\t\tfor _, name := range getSignificantPodNames(podParts[0]) {\n\t\t\tstringContent = strings.ReplaceAll(stringContent, name, anonPodName)\n\t\t}\n\t}\n\n\t// Overwrite the file with the anonymized log\n\terr = f.Truncate(0)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = f.Seek(0, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfdWriter := bufio.NewWriter(f)\n\t_, err = fdWriter.WriteString(stringContent)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfdWriter.Flush()\n\n\treturn nil\n}\n\n// getSignificantPodNames is a helper function that takes in a\n// pod's name and returns the significant subnames that we want\n// to anonymize.  It currently works for pods owned by StatefulSets,\n// ReplicaSets, and Deployments.\nfunc getSignificantPodNames(podName string) []string {\n\t// if the pods ends in an ordinal we can be pretty sure it's\n\t// coming from a StatefulSet.\n\tstatefulSetRegex := regexp.MustCompile(\"(.*)-([0-9]+)$\")\n\t// ReplicasSets, and therefore Deployments because they create\n\t// ReplicaSets, have a hash followed by a 5 character identity\n\t// string attached to the end.\n\treplicaSetRegex := regexp.MustCompile(\"(.*)-([0-9a-f]+)-([0-9a-z]{5})$\")\n\tsigNames := []string{}\n\tswitch {\n\tcase statefulSetRegex.MatchString(podName):\n\t\tmatch := statefulSetRegex.FindStringSubmatch(podName)\n\t\tappName := match[1]\n\t\t// Add the pod name with and without the ordinal\n\t\tsigNames = append(sigNames, podName, appName)\n\tcase replicaSetRegex.MatchString(podName):\n\t\tmatch := replicaSetRegex.FindStringSubmatch(podName)\n\t\tappName := match[1]\n\t\trsName := fmt.Sprintf(\"%s-%s\", appName, match[2])\n\t\t// add the app name with and without generated ReplicaSet hash\n\t\tsigNames = append(sigNames, podName, rsName, appName)\n\tdefault:\n\t\t// For default we don't do anything and will leave sigNames\n\t\t// as an empty slice\n\t}\n\treturn sigNames\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/gather_logs_test.go",
    "content": "package cmd\n\nimport (\n\t\"archive/zip\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os\"\n\t\"regexp\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc Test_gatherLogsZipFiles(t *testing.T) {\n\ttype testcase struct {\n\t\tname string\n\t\t// We use these two slices so it's easier to write tests knowing which\n\t\t// files are expected to exist and which aren't. These slices are combined\n\t\t// prior to calling zipFiles in the tests.\n\t\trealFileNames []string\n\t\tfakeFileNames []string\n\t\tfileDir       string\n\t}\n\ttestCases := []testcase{\n\t\t{\n\t\t\tname:          \"successfulZipAllFiles\",\n\t\t\trealFileNames: []string{\"file1.log\", \"file2.log\", \"diff_name.log\"},\n\t\t\tfakeFileNames: []string{},\n\t\t\tfileDir:       \"testdata/zipDir\",\n\t\t},\n\t\t{\n\t\t\tname:          \"successfulZipSomeFiles\",\n\t\t\trealFileNames: []string{\"file1.log\", \"file2.log\"},\n\t\t\tfakeFileNames: []string{},\n\t\t\tfileDir:       \"testdata/zipDir\",\n\t\t},\n\t\t{\n\t\t\tname:          \"successfulZipNoFiles\",\n\t\t\trealFileNames: []string{},\n\t\t\tfakeFileNames: []string{},\n\t\t\tfileDir:       \"testdata/zipDir\",\n\t\t},\n\t\t{\n\t\t\tname:          \"zipOneIncorrectFile\",\n\t\t\trealFileNames: []string{\"file1.log\", \"file2.log\", \"diff_name.log\"},\n\t\t\tfakeFileNames: []string{\"notreal.log\"},\n\t\t\tfileDir:       \"testdata/zipDir\",\n\t\t},\n\t\t{\n\t\t\tname:          \"zipIncorrectDir\",\n\t\t\trealFileNames: []string{},\n\t\t\tfakeFileNames: []string{\"file1.log\", \"file2.log\", \"diff_name.log\"},\n\t\t\tfileDir:       \"testdata/fakeZipDir\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttcName := tc.name\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\tfileNames := make([]string, 0, len(tc.realFileNames)+len(tc.fakeFileNames))\n\t\t\tfileNames = append(fileNames, tc.realFileNames...)\n\t\t\tfileNames = append(fileNames, tc.fakeFileNames...)\n\t\t\tif tc.fileDir != \"\" {\n\t\t\t\tfor i := range fileNames {\n\t\t\t\t\tfileNames[i] = fmt.Sprintf(\"%s/%s\", tc.fileDir, fileNames[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\toutputDir := t.TempDir()\n\t\t\terr := zipFiles(fileNames, fmt.Sprintf(\"%s/logs.zip\", outputDir))\n\t\t\t// If we put in fakeFileNames, then we verify we get the errors we expect\n\t\t\tif len(tc.fakeFileNames) > 0 {\n\t\t\t\tfor _, name := range tc.fakeFileNames {\n\t\t\t\t\tassert.Contains(t, err.Error(), fmt.Sprintf(\"failed adding %s/%s to zip file\", tc.fileDir, name))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\t// Ensure the files in the zip match the files that wer zipped\n\t\t\tzipReader, err := zip.OpenReader(fmt.Sprintf(\"%s/logs.zip\", outputDir))\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer zipReader.Close()\n\n\t\t\tfor _, f := range zipReader.File {\n\t\t\t\t// Ensure the file was actually supposed to be in the zip\n\t\t\t\tassert.Contains(t, tc.realFileNames, f.Name)\n\n\t\t\t\tfilesEqual, err := checkZipEqual(f, \"testdata/zipDir\")\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.True(t, filesEqual)\n\t\t\t}\n\n\t\t\t// Ensure that only the \"real files\" were added to the zip file\n\t\t\tassert.Equal(t, len(tc.realFileNames), len(zipReader.File))\n\t\t})\n\t}\n}\n\nfunc Test_gatherLogsCopyFiles(t *testing.T) {\n\ttype testcase struct {\n\t\tname        string\n\t\tsrcFileName string\n\t\tfileDir     string\n\t\toutputDir   string\n\t\terrExpected bool\n\t}\n\ttestCases := []testcase{\n\t\t{\n\t\t\tname:        \"successfulCopyFile\",\n\t\t\tsrcFileName: \"file1.log\",\n\t\t\tfileDir:     \"testdata/zipDir\",\n\t\t\toutputDir:   \"\",\n\t\t\terrExpected: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"failSrcFile\",\n\t\t\tsrcFileName: \"fake_file.log\",\n\t\t\tfileDir:     \"testdata/zipDir\",\n\t\t\toutputDir:   \"\",\n\t\t\terrExpected: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"failDstFile\",\n\t\t\tsrcFileName: \"file1.log\",\n\t\t\tfileDir:     \"testdata/zipDir\",\n\t\t\toutputDir:   \"notarealdir\",\n\t\t\terrExpected: true,\n\t\t},\n\t}\n\tfor _, tc := range testCases {\n\t\ttcName := tc.name\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\tif tc.outputDir == \"\" {\n\t\t\t\ttc.outputDir = t.TempDir()\n\t\t\t}\n\t\t\tdstFile := fmt.Sprintf(\"%s/copiedFile.log\", tc.outputDir)\n\t\t\tsrcFile := fmt.Sprintf(\"%s/%s\", tc.fileDir, tc.srcFileName)\n\t\t\terr := copyFiles(dstFile, srcFile)\n\t\t\tif tc.errExpected {\n\t\t\t\tassert.Error(t, err)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\t// when there's no error message, we validate that the file was\n\t\t\t\t// copied correctly\n\t\t\t\tdstContent, err := os.ReadFile(dstFile)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tsrcContent, err := os.ReadFile(srcFile)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tassert.Equal(t, string(dstContent), string(srcContent))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_gatherLogsNoK8s(t *testing.T) {\n\ttype testcase struct {\n\t\tname       string\n\t\toutputFile string\n\t\tdaemons    string\n\t\terrMsg     string\n\t}\n\ttestCases := []testcase{\n\t\t{\n\t\t\tname:       \"successfulZipAllDaemonLogs\",\n\t\t\toutputFile: \"\",\n\t\t\tdaemons:    \"all\",\n\t\t\terrMsg:     \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"successfulZipOnlyRootLogs\",\n\t\t\toutputFile: \"\",\n\t\t\tdaemons:    \"root\",\n\t\t\terrMsg:     \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"successfulZipOnlyConnectorLogs\",\n\t\t\toutputFile: \"\",\n\t\t\tdaemons:    \"user\",\n\t\t\terrMsg:     \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"successfulZipConnectorAndDaemonLogs\",\n\t\t\toutputFile: \"\",\n\t\t\tdaemons:    \"user,root\",\n\t\t\terrMsg:     \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"successfulZipNoDaemonLogs\",\n\t\t\toutputFile: \"\",\n\t\t\tdaemons:    \"None\",\n\t\t\terrMsg:     \"\",\n\t\t},\n\t\t{\n\t\t\tname:       \"incorrectDaemonFlagValue\",\n\t\t\toutputFile: \"\",\n\t\t\tdaemons:    \"notARealFlagValue\",\n\t\t\terrMsg:     \"Options for --daemons are: all, root, user, or None\",\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttcName := tc.name\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\t// Use this time to validate that the zip file says the\n\t\t\t// files inside were modified after the test started.\n\t\t\tstartTime := time.Now()\n\t\t\t// Prepare the context + use our testdata log dir for these tests\n\t\t\tctx := testutil.NewContext(t, false)\n\t\t\ttestLogDir := \"testdata/testLogDir\"\n\t\t\tctx = filelocation.WithAppUserLogDir(ctx, testLogDir)\n\n\t\t\t// this isn't actually used for our unit tests, but is needed for the function\n\t\t\t// when it is getting logs from k8s components\n\t\t\tcmd := &cobra.Command{}\n\n\t\t\t// override the outputFile\n\t\t\toutputDir := t.TempDir()\n\t\t\tif tc.outputFile == \"\" {\n\t\t\t\ttc.outputFile = fmt.Sprintf(\"%s/telepresence_logs.zip\", outputDir)\n\t\t\t}\n\t\t\tstdout := clog.StdLogger(ctx, slog.LevelInfo).Writer()\n\t\t\tstderr := clog.StdLogger(ctx, slog.LevelError).Writer()\n\t\t\tcmd.SetOut(stdout)\n\t\t\tcmd.SetErr(stderr)\n\t\t\tcmd.PersistentFlags().AddFlagSet(global.Flags(ctx, false, false))\n\t\t\tcmd.InitDefaultHelpFlag() // Ensures that persistent flags are merged.\n\t\t\tcmd.SetContext(ctx)\n\t\t\tgl := &gatherLogsCommand{\n\t\t\t\toutputFile: tc.outputFile,\n\t\t\t\tdaemons:    tc.daemons,\n\t\t\t\t// We will test other values of this in our integration tests since\n\t\t\t\t// they require a kubernetes cluster\n\t\t\t\ttrafficAgents:  \"None\",\n\t\t\t\ttrafficManager: false,\n\t\t\t}\n\n\t\t\t// Ensure we can create a zip of the logs\n\t\t\tcacheDir := filelocation.AppUserCacheDir(ctx)\n\t\t\trequire.NoError(t, os.MkdirAll(cacheDir, 0o755))\n\t\t\terr := gl.gatherLogs(cmd, nil)\n\t\t\tif tc.errMsg != \"\" {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), tc.errMsg)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t// Validate that the zip file only contains the files we expect\n\t\t\t\tzipReader, err := zip.OpenReader(tc.outputFile)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tdefer zipReader.Close()\n\n\t\t\t\tvar regexStr string\n\t\t\t\tswitch gl.daemons {\n\t\t\t\tcase \"all\":\n\t\t\t\t\tregexStr = \"cli|connector|daemon\"\n\t\t\t\tcase \"root\":\n\t\t\t\t\tregexStr = \"daemon\"\n\t\t\t\tcase \"user\":\n\t\t\t\t\tregexStr = \"connector\"\n\t\t\t\tcase \"user,root\":\n\t\t\t\t\tregexStr = \"connector|daemon\"\n\t\t\t\tcase \"None\":\n\t\t\t\t\tregexStr = \"a^\" // impossible to match\n\t\t\t\tdefault:\n\t\t\t\t\t// We shouldn't hit this\n\t\t\t\t\tt.Fatal(\"Used an option for daemon that is impossible\")\n\t\t\t\t}\n\t\t\t\tfor _, f := range zipReader.File {\n\t\t\t\t\t// Ensure the file was actually supposed to be in the zip\n\t\t\t\t\tassert.Regexp(t, regexp.MustCompile(regexStr), f.Name)\n\n\t\t\t\t\tfilesEqual, err := checkZipEqual(f, testLogDir)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tassert.True(t, filesEqual)\n\n\t\t\t\t\t// Ensure the zip file metadata is correct (e.g. not the\n\t\t\t\t\t// default which is 1979) that it was modified after the\n\t\t\t\t\t// test started.\n\t\t\t\t\t// This test is incredibly fast (within a second) so we\n\t\t\t\t\t// convert the times to unix timestamps (to get us to\n\t\t\t\t\t// nearest seconds) and ensure the unix timestamp for the\n\t\t\t\t\t// zip file is not less than the unix timestamp for the\n\t\t\t\t\t// start time.\n\t\t\t\t\t// If this ends up being flakey, we can move the start\n\t\t\t\t\t// time out of the test loop and add a sleep for a second\n\t\t\t\t\t// to ensure nothing weird could happen with rounding.\n\t\t\t\t\tassert.False(t,\n\t\t\t\t\t\tf.FileInfo().ModTime().Unix() < startTime.Unix(),\n\t\t\t\t\t\tfmt.Sprintf(\"Start time: %d, file time: %d\",\n\t\t\t\t\t\t\tstartTime.Unix(),\n\t\t\t\t\t\t\tf.FileInfo().ModTime().Unix()))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_gatherLogsGetPodName(t *testing.T) {\n\tpodNames := []string{\n\t\t\"echo-auto-inject-64323-3454.default\",\n\t\t\"echo-easy-141245-23432.ambassador\",\n\t\t\"traffic-manager-123214-2332.ambassador\",\n\t}\n\tpodMapping := []string{\n\t\t\"pod-1.namespace-1\",\n\t\t\"pod-2.namespace-2\",\n\t\t\"traffic-manager.namespace-2\",\n\t}\n\n\t// We need a fresh anonymizer for each test\n\tanonymizer := &anonymizer{\n\t\tnamespaces: make(map[string]string),\n\t\tpodNames:   make(map[string]string),\n\t}\n\t// Get the newPodName for each pod\n\tfor _, podName := range podNames {\n\t\tnewPodName := anonymizer.getPodName(podName)\n\t\trequire.NotEqual(t, podName, newPodName)\n\t}\n\t// Ensure the anonymizer contains the total expected values\n\trequire.Equal(t, 3, len(anonymizer.podNames))\n\trequire.Equal(t, 2, len(anonymizer.namespaces))\n\n\t// Ensure the podNames were anonymized correctly\n\tfor i := range podNames {\n\t\trequire.Equal(t, podMapping[i], anonymizer.podNames[podNames[i]])\n\t}\n\n\t// Ensure the namespaces were anonymized correctly\n\trequire.Equal(t, \"namespace-1\", anonymizer.namespaces[\"default\"])\n\trequire.Equal(t, \"namespace-2\", anonymizer.namespaces[\"ambassador\"])\n}\n\nfunc Test_gatherLogsAnonymizeLogs(t *testing.T) {\n\tanonymizer := &anonymizer{\n\t\tnamespaces: map[string]string{\n\t\t\t\"default\":    \"namespace-1\",\n\t\t\t\"ambassador\": \"namespace-2\",\n\t\t},\n\t\t// these names are specific because they come from the test data\n\t\tpodNames: map[string]string{\n\t\t\t\"echo-auto-inject-6496f77cbd-n86nc.default\":   \"pod-1.namespace-1\",\n\t\t\t\"traffic-manager-5c69859f94-g4ntj.ambassador\": \"traffic-manager.namespace-2\",\n\t\t},\n\t}\n\n\ttestLogDir := \"testdata/testLogDir\"\n\toutputDir := t.TempDir()\n\tfiles := []string{\"echo-auto-inject-6496f77cbd-n86nc\", \"traffic-manager-5c69859f94-g4ntj\"}\n\tfor _, file := range files {\n\t\t// The anonymize function edits files in place\n\t\t// so copy the files before we do that\n\t\tsrcFile := fmt.Sprintf(\"%s/%s\", testLogDir, file)\n\t\tdstFile := fmt.Sprintf(\"%s/%s\", outputDir, file)\n\t\terr := copyFiles(dstFile, srcFile)\n\t\trequire.NoError(t, err)\n\n\t\terr = anonymizer.anonymizeLog(dstFile)\n\t\trequire.NoError(t, err)\n\n\t\t// Now verify things have actually been anonymized\n\t\tanonFile, err := os.ReadFile(dstFile)\n\t\trequire.NoError(t, err)\n\t\trequire.NotContains(t, string(anonFile), \"echo-auto-inject\")\n\t\trequire.NotContains(t, string(anonFile), \"default\")\n\t\trequire.NotContains(t, string(anonFile), \"ambassador\")\n\n\t\t// Both logs make reference to \"echo-auto-inject\" so we\n\t\t// validate that \"pod-1\" appears in both logs\n\t\trequire.Contains(t, string(anonFile), \"pod-1\")\n\t}\n}\n\nfunc Test_gatherLogsSignificantPodNames(t *testing.T) {\n\ttype testcase struct {\n\t\tname    string\n\t\tpodName string\n\t\tresults []string\n\t}\n\ttestCases := []testcase{\n\t\t{\n\t\t\tname:    \"deploymentPod\",\n\t\t\tpodName: \"echo-easy-867b648b88-zjsp2\",\n\t\t\tresults: []string{\n\t\t\t\t\"echo-easy-867b648b88-zjsp2\",\n\t\t\t\t\"echo-easy-867b648b88\",\n\t\t\t\t\"echo-easy\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"statefulSetPod\",\n\t\t\tpodName: \"echo-easy-0\",\n\t\t\tresults: []string{\n\t\t\t\t\"echo-easy-0\",\n\t\t\t\t\"echo-easy\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"unknownName\",\n\t\t\tpodName: \"notarealname\",\n\t\t\tresults: []string{},\n\t\t},\n\t\t{\n\t\t\tname:    \"followPatternNotFullName\",\n\t\t\tpodName: \"a123b\",\n\t\t\tresults: []string{},\n\t\t},\n\t\t{\n\t\t\tname:    \"emptyName\",\n\t\t\tpodName: \"\",\n\t\t\tresults: []string{},\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\ttcName := tc.name\n\t\t// We need a fresh anonymizer for each test\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\tsigPodNames := getSignificantPodNames(tc.podName)\n\t\t\trequire.Equal(t, tc.results, sigPodNames)\n\t\t})\n\t}\n}\n\n// ReadZip reads a zip file and returns the []byte string. Used in tests for\n// checking that a zipped file's contents are correct. Exported since it is\n// also used in telepresence_test.go.\nfunc ReadZip(zippedFile *zip.File) ([]byte, error) {\n\tfileReader, err := zippedFile.Open()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfileContent, err := io.ReadAll(fileReader)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn fileContent, nil\n}\n\n// checkZipEqual is a helper function for validating that the zippedFile in the\n// zip directory matches the file that was used to create the zip.\nfunc checkZipEqual(zippedFile *zip.File, srcLogDir string) (bool, error) {\n\tdstContent, err := ReadZip(zippedFile)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tsrcContent, err := os.ReadFile(fmt.Sprintf(\"%s/%s\", srcLogDir, zippedFile.Name))\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\treturn string(dstContent) == string(srcContent), nil\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/genyaml.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\tapps \"k8s.io/api/apps/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"sigs.k8s.io/yaml\"\n\n\targorollouts \"github.com/datawire/argo-rollouts-go-client/pkg/client/clientset/versioned\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentmap\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\ntype genYAMLCommand struct {\n\toutputFile   string\n\tinputFile    string\n\tconfigFile   string\n\tworkloadName string\n\tnamespace    string\n}\n\nfunc genYAML() *cobra.Command {\n\tinfo := genYAMLCommand{}\n\tcmd := &cobra.Command{\n\t\tUse:  \"genyaml\",\n\t\tArgs: cobra.NoArgs,\n\n\t\tShort: \"Generate YAML for use in kubernetes manifests.\",\n\t\tLong: `Generate traffic-agent yaml for use in kubernetes manifests.\nThis allows the traffic agent to be injected by hand into existing kubernetes manifests.\nFor your modified workload to be valid, you'll have to manually inject annotations, a\ncontainer, and a volume into the workload; you can do this by running \"genyaml config\",\n\"genyaml container\", \"genyaml initcontainer\", \"genyaml annotations\", and \"genyaml volume\".\n\nNOTE: It is recommended that you not do this unless strictly necessary. Instead, we suggest letting\ntelepresence's webhook injector configure the traffic agents on demand.`,\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\tfs := cmd.PersistentFlags()\n\tfs.StringVarP(&info.outputFile, \"output\", \"o\", \"-\",\n\t\t\"Path to the file to place the output in. Defaults to '-' which means stdout.\")\n\tcmd.AddCommand(\n\t\tgenConfigMapSubCommand(&info),\n\t\tgenContainerSubCommand(&info),\n\t\tgenInitContainerSubCommand(&info),\n\t\tgenVAnnotationsSubCommand(&info),\n\t\tgenVolumeSubCommand(&info),\n\t)\n\treturn cmd\n}\n\nfunc getInput(inputFile string) ([]byte, error) {\n\tvar f io.ReadCloser\n\tif inputFile == \"-\" {\n\t\tf = os.Stdin\n\t} else {\n\t\tvar err error\n\t\tif f, err = os.Open(inputFile); err != nil {\n\t\t\treturn nil, errcat.User.Errorf(err, \"unable to open input file %q\", inputFile)\n\t\t}\n\t\tdefer f.Close()\n\t}\n\tb, err := io.ReadAll(f)\n\tif err != nil {\n\t\treturn nil, errcat.User.Errorf(err, \"error reading from %s\", inputFile)\n\t}\n\treturn b, nil\n}\n\nfunc (i *genYAMLCommand) getOutputWriter() (io.WriteCloser, error) {\n\tif i.outputFile == \"-\" {\n\t\treturn os.Stdout, nil\n\t}\n\tf, err := os.Create(i.outputFile)\n\tif err != nil {\n\t\treturn nil, errcat.User.Errorf(err, \"unable to open output file %s\", i.outputFile)\n\t}\n\treturn f, nil\n}\n\nfunc (i *genYAMLCommand) loadConfigMapEntry() (*agentconfig.Sidecar, error) {\n\tif i.configFile == \"\" {\n\t\treturn nil, errcat.User.New(\"--agent <agent config> must be provided\")\n\t}\n\tb, err := getInput(i.configFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar cfg agentconfig.Sidecar\n\tb, err = yaml.YAMLToJSON(b)\n\tif err == nil {\n\t\terr = json.Unmarshal(b, &cfg)\n\t}\n\tif err != nil {\n\t\treturn nil, errcat.User.Newf(\"unable to parse config %s: %w\", i.configFile, err)\n\t}\n\treturn &cfg, nil\n}\n\nfunc (i *genYAMLCommand) loadWorkload(ctx context.Context) (k8sapi.Workload, error) {\n\tif i.inputFile == \"\" {\n\t\tif i.workloadName == \"\" {\n\t\t\treturn nil, errcat.User.New(\"either --input or --workload must be provided\")\n\t\t}\n\t\treturn k8sapi.GetWorkload(ctx, i.workloadName, i.namespace, \"\")\n\t}\n\tb, err := getInput(i.inputFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tscheme := runtime.NewScheme()\n\tscheme.AddKnownTypes(schema.GroupVersion{Group: apps.GroupName, Version: \"v1\"}, &apps.StatefulSet{}, &apps.Deployment{}, &apps.ReplicaSet{})\n\tcodecFactory := serializer.NewCodecFactory(scheme)\n\tdeserializer := codecFactory.UniversalDeserializer()\n\n\tobj, kind, err := deserializer.Decode(b, nil, nil)\n\tif err != nil {\n\t\treturn nil, errcat.User.Newf(\"unable to parse yaml in %s: %w\", i.inputFile, err)\n\t}\n\twl, err := k8sapi.WrapWorkload(obj)\n\tif err != nil {\n\t\treturn nil, errcat.User.Newf(\"unexpected object of kind %s; please pass in a Deployment, ReplicaSet, or StatefulSet\", kind)\n\t}\n\tif wl.GetNamespace() == \"\" {\n\t\tif d, ok := k8sapi.DeploymentImpl(wl); ok {\n\t\t\td.Namespace = i.namespace\n\t\t} else if r, ok := k8sapi.ReplicaSetImpl(wl); ok {\n\t\t\tr.Namespace = i.namespace\n\t\t} else if s, ok := k8sapi.StatefulSetImpl(wl); ok {\n\t\t\ts.Namespace = i.namespace\n\t\t}\n\t}\n\treturn wl, nil\n}\n\nfunc (i *genYAMLCommand) writeObjToOutput(obj any) error {\n\tdoc, err := json.Marshal(obj)\n\tif err == nil {\n\t\tdoc, err = yaml.JSONToYAML(doc)\n\t}\n\tif err != nil {\n\t\treturn errcat.User.Errorf(err, \"unable to marshal agent container\")\n\t}\n\tw, err := i.getOutputWriter()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer w.Close()\n\t_, err = w.Write(doc)\n\tif err != nil {\n\t\treturn errcat.User.Errorf(err, \"unable to write to output %s\", i.outputFile)\n\t}\n\treturn nil\n}\n\nfunc (i *genYAMLCommand) WithJoinedClientSetInterface(ctx context.Context, flagMap map[string]string) (context.Context, error) {\n\tconfigFlags := genericclioptions.NewConfigFlags(false)\n\tfs := pflag.NewFlagSet(\"\", 0)\n\tconfigFlags.AddFlags(fs)\n\tfor k, v := range flagMap {\n\t\tif err := fs.Set(k, v); err != nil {\n\t\t\treturn nil, errcat.User.Errorf(err, \"error processing kubectl flag --%s=%s\", k, v)\n\t\t}\n\t}\n\n\tconfigLoader := configFlags.ToRawKubeConfigLoader()\n\trestConfig, err := configLoader.ClientConfig()\n\tif err != nil {\n\t\treturn nil, errcat.Config.New(err)\n\t}\n\n\tconfig, err := configLoader.RawConfig()\n\tif err != nil {\n\t\treturn nil, errcat.Config.New(err)\n\t}\n\tif len(config.Contexts) == 0 {\n\t\treturn nil, errcat.Config.New(\"kubeconfig has no context definition\")\n\t}\n\n\tctxName := flagMap[\"context\"]\n\tif ctxName == \"\" {\n\t\tctxName = config.CurrentContext\n\t}\n\tc, ok := config.Contexts[ctxName]\n\tif !ok {\n\t\treturn nil, errcat.Config.Newf(\"context %q does not exist in the kubeconfig\", ctxName)\n\t}\n\ti.namespace = flagMap[\"namespace\"]\n\tif i.namespace == \"\" {\n\t\ti.namespace = c.Namespace\n\t\tif i.namespace == \"\" {\n\t\t\ti.namespace = \"default\"\n\t\t}\n\t}\n\tcs, err := kubernetes.NewForConfig(restConfig)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tif acs, err := argorollouts.NewForConfig(restConfig); err == nil {\n\t\treturn k8sapi.WithJoinedClientSetInterface(ctx, cs, acs), nil\n\t}\n\treturn ctx, err\n}\n\ntype genConfigMap struct {\n\tagentmap.GeneratorConfig\n\t*genYAMLCommand\n}\n\nfunc allKubeFlags() *pflag.FlagSet {\n\tkubeFlags := pflag.NewFlagSet(\"Kubernetes flags\", 0)\n\tkubeConfig := genericclioptions.NewConfigFlags(false)\n\tkubeConfig.AddFlags(kubeFlags)\n\treturn kubeFlags\n}\n\nfunc genConfigMapSubCommand(yamlInfo *genYAMLCommand) *cobra.Command {\n\tkubeFlags := allKubeFlags()\n\tinfo := genConfigMap{genYAMLCommand: yamlInfo}\n\tcmd := &cobra.Command{\n\t\tUse:   \"config\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Generate YAML for the agent's entry in the telepresence-agents configmap.\",\n\t\tLong:  \"Generate YAML for the agent's entry in the telepresence-agents configmap. See genyaml for more info on what this means\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn info.run(cmd, flags.Map(kubeFlags))\n\t\t},\n\t}\n\n\tfs := cmd.Flags()\n\tfs.StringVarP(&info.inputFile, \"input\", \"i\", \"\",\n\t\t\"Path to the yaml containing the workload definition (i.e. Deployment, StatefulSet, etc). Pass '-' for stdin.. Mutually exclusive to --workload\")\n\tfs.StringVarP(&info.workloadName, \"workload\", \"w\", \"\",\n\t\t\"Name of the workload. If given, the workload will be retrieved from the cluster, mutually exclusive to --input\")\n\tfs.Uint16Var(&info.AgentPort, \"agent-port\", 9900,\n\t\t\"The port number you wish the agent to listen on.\")\n\tfs.StringVar(&info.QualifiedAgentImage, \"agent-image\", \"ghcr.io/telepresenceio/tel2:<current version>\",\n\t\t`The qualified name of the agent image`)\n\tfs.Uint16Var(&info.ManagerPort, \"manager-port\", 8081,\n\t\t`The traffic-manager API port`)\n\tfs.StringVar(&info.ManagerNamespace, \"manager-namespace\", \"ambassador\",\n\t\t`The traffic-manager namespace`)\n\tfs.TextVar(&info.LogLevel, \"loglevel\", slog.LevelInfo,\n\t\t`The loglevel for the generated traffic-agent sidecar`)\n\tfs.AddFlagSet(kubeFlags)\n\treturn cmd\n}\n\nfunc (g *genConfigMap) generateConfigMap(ctx context.Context, wl k8sapi.Workload) (*agentconfig.Sidecar, error) {\n\tg.WatchRetryInterval = 10 * time.Second\n\tac, err := g.Generate(ctx, wl, nil)\n\tif err != nil {\n\t\treturn nil, errcat.NoDaemonLogs.New(err)\n\t}\n\treturn ac, nil\n}\n\nfunc (g *genConfigMap) run(cmd *cobra.Command, kubeFlags map[string]string) error {\n\t// Resolve the placeholder in the default agent image with the actual version.\n\tg.QualifiedAgentImage = strings.ReplaceAll(g.QualifiedAgentImage, \"<current version>\", client.Semver().FinalizeVersion())\n\tctx, err := g.WithJoinedClientSetInterface(cmd.Context(), kubeFlags)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\twl, err := g.loadWorkload(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcfg, err := g.generateConfigMap(ctx, wl)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcfg.Manual = true\n\treturn g.writeObjToOutput(cfg)\n}\n\ntype genContainerInfo struct {\n\t*genYAMLCommand\n}\n\nfunc genContainerSubCommand(yamlInfo *genYAMLCommand) *cobra.Command {\n\tkubeFlags := allKubeFlags()\n\tinfo := genContainerInfo{genYAMLCommand: yamlInfo}\n\tcmd := &cobra.Command{\n\t\tUse:   \"container\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Generate YAML for the traffic-agent container.\",\n\t\tLong:  \"Generate YAML for the traffic-agent container. See genyaml for more info on what this means\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn info.run(cmd, flags.Map(kubeFlags))\n\t\t},\n\t}\n\tfs := cmd.Flags()\n\tfs.StringVarP(&info.inputFile, \"input\", \"i\", \"\",\n\t\t\"Optional path to the yaml containing the workload definition (i.e. Deployment, StatefulSet, etc). Pass '-' for stdin. Loaded from cluster by default\")\n\tfs.StringVarP(&info.configFile, \"agent\", \"a\", \"\", \"Path to the yaml containing the generated agent config\")\n\tfs.AddFlagSet(kubeFlags)\n\treturn cmd\n}\n\nfunc (g *genContainerInfo) run(cmd *cobra.Command, kubeFlags map[string]string) error {\n\tctx, err := g.WithJoinedClientSetInterface(cmd.Context(), kubeFlags)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcm, err := g.loadConfigMapEntry()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif g.inputFile == \"\" {\n\t\tg.workloadName = cm.WorkloadName\n\t}\n\n\twl, err := g.loadWorkload(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Sanity check\n\tif wl.GetName() != cm.WorkloadName {\n\t\treturn errcat.User.Newf(\"name %q of loaded workload is different from %q loaded configmap entry\", wl.GetName(), cm.WorkloadName)\n\t}\n\tif wl.GetKind() != cm.WorkloadKind {\n\t\treturn errcat.User.Newf(\"kind %q of loaded workload is different from %q loaded configmap entry\", wl.GetKind(), cm.WorkloadKind)\n\t}\n\n\tab := agentconfig.ContainerBuilder{\n\t\tPod:    wl.GetPodTemplate(),\n\t\tConfig: cm,\n\t}\n\tagentContainer, _, err := ab.AgentContainer(ctx)\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\treturn g.writeObjToOutput(agentContainer)\n}\n\ntype genInitContainerInfo struct {\n\t*genYAMLCommand\n}\n\nfunc genInitContainerSubCommand(yamlInfo *genYAMLCommand) *cobra.Command {\n\tkubeFlags := allKubeFlags()\n\tinfo := genInitContainerInfo{genYAMLCommand: yamlInfo}\n\tcmd := &cobra.Command{\n\t\tUse:   \"initcontainer\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Generate YAML for the traffic-agent init container.\",\n\t\tLong:  \"Generate YAML for the traffic-agent init container. See genyaml for more info on what this means\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn info.run(cmd, flags.Map(kubeFlags))\n\t\t},\n\t}\n\tfs := cmd.Flags()\n\tfs.StringVarP(&info.configFile, \"agent\", \"a\", \"\", \"Path to the yaml containing the generated agent config\")\n\tfs.AddFlagSet(kubeFlags)\n\treturn cmd\n}\n\nfunc (g *genInitContainerInfo) run(*cobra.Command, map[string]string) error {\n\tcm, err := g.loadConfigMapEntry()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, cc := range cm.Containers {\n\t\tfor _, ic := range cc.Intercepts {\n\t\t\tif ic.Headless || ic.TargetPortNumeric {\n\t\t\t\treturn g.writeObjToOutput(agentconfig.InitContainer(cm))\n\t\t\t}\n\t\t}\n\t}\n\treturn errcat.User.New(\"deployment does not need an init container\")\n}\n\ntype genAnnotationsInfo struct {\n\t*genYAMLCommand\n}\n\nfunc genVAnnotationsSubCommand(yamlInfo *genYAMLCommand) *cobra.Command {\n\tinfo := genAnnotationsInfo{genYAMLCommand: yamlInfo}\n\tcmd := &cobra.Command{\n\t\tUse:   \"annotations\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Generate YAML for the pod template metadata annotations.\",\n\t\tLong:  \"Generate YAML for the pod template metadata annotations. See genyaml for more info on what this means\",\n\t\tRunE: func(*cobra.Command, []string) error {\n\t\t\treturn info.run()\n\t\t},\n\t}\n\tfs := cmd.Flags()\n\tfs.StringVarP(&info.configFile, \"agent\", \"a\", \"\", \"Path to the yaml containing the generated agent config\")\n\treturn cmd\n}\n\nfunc (g *genAnnotationsInfo) run() error {\n\tcm, err := g.loadConfigMapEntry()\n\tif err != nil {\n\t\treturn err\n\t}\n\tcmJSON, err := agentconfig.MarshalTight(cm)\n\tif err != nil {\n\t\treturn err\n\t}\n\tanns := map[string]string{\n\t\tannotation.InjectTrafficAgent: \"enabled\",\n\t\tannotation.ManuallyInjected:   \"true\",\n\t\tannotation.Config:             cmJSON,\n\t}\n\treturn g.writeObjToOutput(anns)\n}\n\ntype genVolumeInfo struct {\n\t*genYAMLCommand\n}\n\nfunc genVolumeSubCommand(yamlInfo *genYAMLCommand) *cobra.Command {\n\tinfo := genVolumeInfo{genYAMLCommand: yamlInfo}\n\tkubeFlags := allKubeFlags()\n\tcmd := &cobra.Command{\n\t\tUse:   \"volume\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Generate YAML for the traffic-agent volume.\",\n\t\tLong:  \"Generate YAML for the traffic-agent volume. See genyaml for more info on what this means\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn info.run()\n\t\t},\n\t}\n\tfs := cmd.Flags()\n\tfs.StringVarP(&info.inputFile, \"input\", \"i\", \"\",\n\t\t\"Optional path to the yaml containing the workload definition (i.e. Deployment, StatefulSet, etc). Pass '-' for stdin. Loaded from cluster by default\")\n\tfs.StringVarP(&info.configFile, \"agent\", \"a\", \"\", \"Path to the yaml containing the generated agent config\")\n\tfs.AddFlagSet(kubeFlags)\n\treturn cmd\n}\n\nfunc (g *genVolumeInfo) run() error {\n\tvolumes, err := agentconfig.AgentVolumes(g.workloadName, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn g.writeObjToOutput(&volumes)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/helm.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/helm\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nfunc helmCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   \"helm command [flags]\",\n\t\tShort: `Helm commands using the embedded Telepresence Helm chart.`,\n\t}\n\tcmd.AddCommand(helmInstall(), helmUpgrade(), helmUninstall(), helmLint(), helmVersion())\n\treturn cmd\n}\n\ntype HelmCommand struct {\n\thelm.Request\n\tAllValues map[string]any\n\trq        *daemon.CobraRequest\n}\n\nvar (\n\tHelmInstallExtendFlagsFunc func(*pflag.FlagSet)                                      //nolint:gochecknoglobals // extension point\n\tHelmInstallPrologFunc      func(context.Context, *pflag.FlagSet, *HelmCommand) error //nolint:gochecknoglobals // extension point\n)\n\nfunc helmInstall() *cobra.Command {\n\tvar upgrade bool\n\n\tha := &HelmCommand{\n\t\tRequest: helm.Request{\n\t\t\tType: helm.Install,\n\t\t},\n\t}\n\tcmd := &cobra.Command{\n\t\tUse:   \"install\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Install telepresence traffic manager\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif upgrade {\n\t\t\t\tha.Request.Type = helm.Upgrade\n\t\t\t}\n\t\t\treturn ha.run(cmd, args)\n\t\t},\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\n\tflags := cmd.Flags()\n\tflags.BoolVarP(&ha.NoHooks, \"no-hooks\", \"\", false, \"prevent hooks from running during install\")\n\tflags.BoolVarP(&upgrade, \"upgrade\", \"u\", false, \"replace the traffic manager if it already exists\")\n\tflags.BoolVar(&ha.CreateNamespace, \"create-namespace\", true, \"create a namespace for the traffic-manager if not present\")\n\tflags.StringVar(&ha.Version, \"version\", \"\", \"the telepresence version if different from the client's version. May be a range (e.g. ^2.21.0)\")\n\tha.addValueSettingFlags(flags)\n\tuf := flags.Lookup(\"upgrade\")\n\tuf.Hidden = true\n\tuf.Deprecated = `Use \"telepresence helm upgrade\" instead of \"telepresence helm install --upgrade\"`\n\tha.rq = daemon.InitRequest(cmd)\n\treturn cmd\n}\n\nfunc helmUpgrade() *cobra.Command {\n\tha := &HelmCommand{\n\t\tRequest: helm.Request{\n\t\t\tType: helm.Upgrade,\n\t\t},\n\t}\n\tcmd := &cobra.Command{\n\t\tUse:   \"upgrade\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Upgrade telepresence traffic manager\",\n\t\tRunE:  ha.run,\n\t}\n\n\tflags := cmd.Flags()\n\tha.addValueSettingFlags(flags)\n\tflags.BoolVarP(&ha.NoHooks, \"no-hooks\", \"\", false, \"disable pre/post upgrade hooks\")\n\tflags.BoolVarP(&ha.ResetValues, \"reset-values\", \"\", false, \"when upgrading, reset the values to the ones built into the chart\")\n\tflags.BoolVarP(&ha.ReuseValues, \"reuse-values\", \"\", false,\n\t\t\"when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f\")\n\tflags.BoolVarP(&ha.CreateNamespace, \"create-namespace\", \"\", true, \"create the release namespace if not present\")\n\tflags.StringVar(&ha.Version, \"version\", \"\", \"the telepresence version if different from the client's version. May be a range (e.g. ^2.21.0)\")\n\tha.rq = daemon.InitRequest(cmd)\n\treturn cmd\n}\n\nfunc (ha *HelmCommand) addValueSettingFlags(flags *pflag.FlagSet) {\n\tflags.StringArrayVarP(&ha.ValueFiles, \"values\", \"f\", []string{},\n\t\t\"specify values in a YAML file or a URL (can specify multiple)\")\n\tflags.StringArrayVarP(&ha.Values, \"set\", \"\", []string{},\n\t\t\"specify a value as a.b=v (can specify multiple or separate values with commas: a.b=v1,a.c=v2)\")\n\tflags.StringArrayVarP(&ha.FileValues, \"set-file\", \"\", []string{},\n\t\t\"set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)\")\n\tflags.StringArrayVarP(&ha.JSONValues, \"set-json\", \"\", []string{},\n\t\t\"set JSON values on the command line (can specify multiple or separate values with commas: a.b=jsonval1,a.c=jsonval2)\")\n\tflags.StringArrayVarP(&ha.StringValues, \"set-string\", \"\", []string{},\n\t\t\"set STRING values on the command line (can specify multiple or separate values with commas: a.b=val1,a.c=val2)\")\n\tif HelmInstallExtendFlagsFunc != nil {\n\t\tHelmInstallExtendFlagsFunc(flags)\n\t}\n}\n\nfunc helmUninstall() *cobra.Command {\n\tha := &HelmCommand{\n\t\tRequest: helm.Request{\n\t\t\tType: helm.Uninstall,\n\t\t},\n\t}\n\tcmd := &cobra.Command{\n\t\tUse:   \"uninstall\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Uninstall telepresence traffic manager\",\n\t\tRunE:  ha.run,\n\t}\n\tflags := cmd.Flags()\n\tflags.BoolVarP(&ha.NoHooks, \"no-hooks\", \"\", false, \"prevent hooks from running during uninstallation\")\n\tha.rq = daemon.InitRequest(cmd)\n\treturn cmd\n}\n\nfunc helmLint() *cobra.Command {\n\tha := &HelmCommand{\n\t\tRequest: helm.Request{\n\t\t\tType: helm.Lint,\n\t\t},\n\t}\n\tcmd := &cobra.Command{\n\t\tUse:   \"lint\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Verify the embedded telepresence Helm chart\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn ha.run(cmd, args)\n\t\t},\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\n\tflags := cmd.Flags()\n\tflags.StringVar(&ha.Version, \"version\", \"\", \"the telepresence version if different from the client's version. May be a range (e.g. ^2.21.0)\")\n\tha.addValueSettingFlags(flags)\n\tha.rq = daemon.InitRequest(cmd)\n\treturn cmd\n}\n\nfunc helmVersion() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"version\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Print the version of the Helm client\",\n\t\tRunE: func(cmd *cobra.Command, _ []string) (err error) {\n\t\t\tioutil.Println(cmd.OutOrStdout(), version.HelmVersion)\n\t\t\treturn nil\n\t\t},\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n}\n\nfunc (ha *HelmCommand) Type() helm.RequestType {\n\treturn ha.Request.Type\n}\n\nfunc (ha *HelmCommand) run(cmd *cobra.Command, _ []string) (err error) {\n\tif err = ha.rq.CommitFlags(cmd); err != nil {\n\t\treturn err\n\t}\n\tctx := cmd.Context()\n\tif HelmInstallPrologFunc != nil {\n\t\tif err = HelmInstallPrologFunc(ctx, cmd.Flags(), ha); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn ha.Run(ctx, ha.rq.ConnectRequest)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/ingest.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ingest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n)\n\nfunc ingestCmd() *cobra.Command {\n\tic := &ingest.Command{}\n\tcmd := &cobra.Command{\n\t\tUse:   \"ingest [flags] <name> [-- [[docker run flags] <image name>] OR [<command>]] args...]\",\n\t\tArgs:  cobra.MinimumNArgs(1),\n\t\tShort: \"Ingest a container\",\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session:           ann.Required,\n\t\t\tann.UpdateCheckFormat: ann.Tel2,\n\t\t},\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t\tRunE:              ic.Run,\n\t\tValidArgsFunction: intercept.ValidArgs, // a list that this command shares with intercept\n\t}\n\tic.AddFlags(cmd)\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/intercept.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n)\n\nfunc interceptCmd() *cobra.Command {\n\tic := &intercept.Command{}\n\tcmd := &cobra.Command{\n\t\tUse:   \"intercept [flags] <name> [-- [[docker run flags] <image name>] OR [<command>]] args...]\",\n\t\tArgs:  cobra.MinimumNArgs(1),\n\t\tShort: \"Intercept a service\",\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session:           ann.Required,\n\t\t\tann.UpdateCheckFormat: ann.Tel2,\n\t\t},\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t\tRunE:              ic.Run,\n\t\tValidArgsFunction: intercept.ValidArgs,\n\t}\n\tic.AddInterceptFlags(cmd)\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/kubeauth.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/authenticator\"\n)\n\nfunc kubeauthCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:               \"kubeauth\",\n\t\tArgs:              cobra.ExactArgs(2),\n\t\tShort:             \"Resolve kubeconfig context using gRPC to the kubeauth daemon\",\n\t\tRunE:              authenticateContext,\n\t\tHidden:            true,\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\treturn cmd\n}\n\nfunc authenticateContext(cmd *cobra.Command, args []string) (err error) {\n\tctx := cmd.Context()\n\tcontextName := args[0]\n\tserverAddr := args[1]\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t}\n\t}()\n\tvar conn *grpc.ClientConn\n\tif conn, err = grpc.NewClient(serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials())); err != nil {\n\t\treturn fmt.Errorf(\"failed to dial GRPC server %s: %w\", serverAddr, err)\n\t}\n\tdefer conn.Close()\n\n\tac := rpc.NewAuthenticatorClient(conn)\n\tvar res *rpc.GetContextExecCredentialsResponse\n\tif res, err = ac.GetContextExecCredentials(ctx, &rpc.GetContextExecCredentialsRequest{ContextName: contextName}); err != nil {\n\t\treturn fmt.Errorf(\"failed to get exec credentials: %w\", err)\n\t}\n\tif _, err = os.Stdout.Write(res.RawCredentials); err != nil {\n\t\terr = fmt.Errorf(\"failed to print raw credentials: %w\", err)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/leave.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n)\n\nfunc leaveCmd() *cobra.Command {\n\tvar containerName string\n\tcmd := &cobra.Command{\n\t\tUse:  \"leave [flags] <intercept_name>\",\n\t\tArgs: cobra.ExactArgs(1),\n\n\t\tShort: \"Remove existing intercept\",\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Required,\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif err := connect.InitCommand(cmd); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer progress.Stop(cmd.Context())\n\t\t\treturn disengage(cmd.Context(), strings.TrimSpace(args[0]), containerName)\n\t\t},\n\t\tValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\t\tshellCompDir := cobra.ShellCompDirectiveNoFileComp\n\t\t\tif len(args) != 0 {\n\t\t\t\treturn nil, shellCompDir\n\t\t\t}\n\t\t\tif err := connect.InitCommand(cmd); err != nil {\n\t\t\t\treturn nil, shellCompDir | cobra.ShellCompDirectiveError\n\t\t\t}\n\t\t\tctx := cmd.Context()\n\t\t\tuserD := daemon.MustGetUserClient(ctx)\n\t\t\tresp, err := userD.List(ctx, &connector.ListRequest{\n\t\t\t\tFilter: connector.ListRequest_INTERCEPTS | connector.ListRequest_REPLACEMENTS | connector.ListRequest_INGESTS,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, shellCompDir | cobra.ShellCompDirectiveError\n\t\t\t}\n\t\t\tif len(resp.Workloads) == 0 {\n\t\t\t\treturn nil, shellCompDir\n\t\t\t}\n\n\t\t\tvar completions []string\n\t\t\tfor _, wl := range resp.Workloads {\n\t\t\t\tfor _, ii := range wl.InterceptInfo {\n\t\t\t\t\tname := ii.Spec.Name\n\t\t\t\t\tif strings.HasPrefix(name, toComplete) {\n\t\t\t\t\t\tcompletions = append(completions, name)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor _, ig := range wl.IngestInfo {\n\t\t\t\t\tname := ig.Workload\n\t\t\t\t\tif strings.HasPrefix(name, toComplete) {\n\t\t\t\t\t\tcompletions = append(completions, name)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn completions, shellCompDir\n\t\t},\n\t}\n\tcmd.Flags().StringVarP(&containerName, \"container\", \"c\", \"\", \"Container name\")\n\treturn cmd\n}\n\nfunc disengage(ctx context.Context, name, container string) error {\n\tuserD := daemon.MustGetUserClient(ctx)\n\n\tvar ic *manager.InterceptInfo\n\tvar ig *connector.IngestInfo\n\tvar env map[string]string\n\tvar err error\n\ticName := name\n\tif container != \"\" {\n\t\ticName += \"/\" + container\n\t}\n\tic, err = userD.GetIntercept(ctx, &manager.GetInterceptRequest{Name: icName})\n\tif err != nil && status.Code(err) != codes.NotFound {\n\t\treturn grpc.FromGRPC(err)\n\t}\n\n\tif ic == nil {\n\t\tig, err = userD.GetIngest(ctx, &connector.IngestIdentifier{\n\t\t\tWorkloadName:  name,\n\t\t\tContainerName: container,\n\t\t})\n\t\tif err != nil {\n\t\t\tif status.Code(err) != codes.NotFound {\n\t\t\t\treturn grpc.FromGRPC(err)\n\t\t\t}\n\n\t\t\t// User probably misspelled the name of the replace/intercept/ingest\n\t\t\tmsg := fmt.Sprintf(\"Found no replace, intercept, or ingest named %q\", name)\n\t\t\tif container != \"\" {\n\t\t\t\tmsg = fmt.Sprintf(\"%s with container %q\", msg, container)\n\t\t\t}\n\t\t\treturn errcat.User.New(msg)\n\t\t}\n\t\tenv = ig.Environment\n\t} else {\n\t\tenv = ic.Environment\n\t}\n\n\tif userD.DaemonID().Containerized {\n\t\thandlerContainer, stopContainer := env[\"TELEPRESENCE_HANDLER_CONTAINER_NAME\"]\n\t\tif stopContainer {\n\t\t\t// Stop the handler's container. The daemon is most likely running in another\n\t\t\t// container, and won't be able to.\n\t\t\terr = docker.StopContainer(ctx, handlerContainer)\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tif ic != nil {\n\t\t_, err = userD.RemoveIntercept(ctx, &manager.RemoveInterceptRequest2{Name: ic.Spec.Name})\n\t} else {\n\t\t_, err = userD.LeaveIngest(ctx, &connector.IngestIdentifier{\n\t\t\tWorkloadName:  ig.Workload,\n\t\t\tContainerName: ig.Container,\n\t\t})\n\t}\n\treturn grpc.FromGRPC(err)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/list.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\nconst (\n\tincludeIntercepts = iota\n\tincludeIngests\n\tincludeReplacements\n\tincludeWiretaps\n)\n\ntype listCommand struct {\n\tinclusions [4]bool\n\tonlyAgents bool\n\tdebug      bool\n\tnamespace  string\n\twatch      bool\n}\n\nfunc list() *cobra.Command {\n\ts := &listCommand{}\n\tcmd := &cobra.Command{\n\t\tUse:  \"list\",\n\t\tArgs: cobra.NoArgs,\n\n\t\tShort: \"List current intercepts\",\n\t\tRunE:  s.list,\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Required,\n\t\t},\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\tflags := cmd.Flags()\n\tflags.BoolVarP(&s.inclusions[includeIntercepts], \"intercepts\", \"i\", false, \"intercepts\")\n\tflags.BoolVarP(&s.inclusions[includeIngests], \"ingests\", \"g\", false, \"ingests\")\n\tflags.BoolVarP(&s.inclusions[includeReplacements], \"replacements\", \"r\", false, \"replacements\")\n\tflags.BoolVarP(&s.inclusions[includeWiretaps], \"wiretaps\", \"t\", false, \"wiretaps\")\n\tflags.BoolVarP(&s.onlyAgents, \"agents\", \"a\", false, \"with installed agents only\")\n\tflags.BoolVar(&s.debug, \"debug\", false, \"include debugging information\")\n\tflags.StringVarP(&s.namespace, \"namespace\", \"n\", \"\", \"If present, the namespace scope for this CLI request\")\n\n\tflags.BoolP(\"only-interceptable\", \"o\", false, \"\")\n\tof := flags.Lookup(\"only-interceptable\")\n\tof.Hidden = true\n\tof.Deprecated = \"Redundant since all workloads are eligible for ingest, intercept, or replace\"\n\n\tflags.BoolVarP(&s.watch, \"watch\", \"w\", false, \"watch a namespace. --agents and --intercepts are disabled if this flag is set\")\n\twf := flags.Lookup(\"watch\")\n\twf.Hidden = true\n\twf.Deprecated = `Use \"--output json-stream\" instead of \"--watch\"`\n\n\t_ = cmd.RegisterFlagCompletionFunc(\"namespace\", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t\tshellCompDir := cobra.ShellCompDirectiveNoFileComp\n\t\tif err := connect.InitCommand(cmd); err != nil {\n\t\t\tshellCompDir |= cobra.ShellCompDirectiveError\n\t\t\treturn nil, shellCompDir\n\t\t}\n\t\tctx := cmd.Context()\n\t\tuserD := daemon.MustGetUserClient(ctx)\n\t\tresp, err := userD.GetNamespaces(ctx, &connector.GetNamespacesRequest{\n\t\t\tForClientAccess: false,\n\t\t\tPrefix:          toComplete,\n\t\t})\n\t\tif err != nil {\n\t\t\tclog.Debugf(cmd.Context(), \"error getting namespaces: %v\", err)\n\t\t\tshellCompDir |= cobra.ShellCompDirectiveError\n\t\t\treturn nil, shellCompDir\n\t\t}\n\t\treturn resp.Namespaces, shellCompDir\n\t})\n\treturn cmd\n}\n\ntype watchWorkloadStreamResponse struct {\n\tworkloadInfoSnapshot *connector.WorkloadInfoSnapshot\n\terr                  error\n}\n\n// list requests a list current intercepts from the daemon.\nfunc (s *listCommand) list(cmd *cobra.Command, _ []string) error {\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\treturn err\n\t}\n\tdefer progress.Stop(cmd.Context())\n\tstdout := cmd.OutOrStdout()\n\tctx := cmd.Context()\n\tuserD := daemon.MustGetUserClient(ctx)\n\tfilter := connector.ListRequest_UNSPECIFIED\n\tfor i := range s.inclusions {\n\t\tif s.inclusions[i] {\n\t\t\tswitch i {\n\t\t\tcase includeIntercepts:\n\t\t\t\tfilter |= connector.ListRequest_INTERCEPTS\n\t\t\tcase includeReplacements:\n\t\t\t\tfilter |= connector.ListRequest_REPLACEMENTS\n\t\t\tcase includeIngests:\n\t\t\t\tfilter |= connector.ListRequest_INGESTS\n\t\t\tcase includeWiretaps:\n\t\t\t\tfilter |= connector.ListRequest_WIRETAPS\n\t\t\t}\n\t\t}\n\t}\n\tif filter == connector.ListRequest_UNSPECIFIED && s.onlyAgents {\n\t\tfilter = connector.ListRequest_INSTALLED_AGENTS\n\t}\n\n\tcfg := client.GetConfig(ctx)\n\tmaxRecSize := int64(1024 * 1024 * 20) // Default to 20 Mb here. List can be quit long.\n\tif mz := cfg.Grpc().MaxReceiveSize(); mz > 0 {\n\t\tif mz > maxRecSize {\n\t\t\tmaxRecSize = mz\n\t\t}\n\t}\n\n\tformattedOutput := output.WantsFormatted(cmd)\n\tif !output.WantsStream(cmd) {\n\t\tr, err := userD.List(ctx, &connector.ListRequest{Filter: filter, Namespace: s.namespace}, grpc.MaxCallRecvMsgSize(int(maxRecSize)))\n\t\tif err != nil {\n\t\t\treturn tpGrpc.FromGRPC(err)\n\t\t}\n\t\ts.printList(ctx, r.Workloads, stdout, formattedOutput)\n\t\treturn nil\n\t}\n\n\tstream, streamErr := userD.WatchWorkloads(ctx, &connector.WatchWorkloadsRequest{Namespaces: []string{s.namespace}}, grpc.MaxCallRecvMsgSize(int(maxRecSize)))\n\tif streamErr != nil {\n\t\treturn tpGrpc.FromGRPC(streamErr)\n\t}\n\n\tch := make(chan *watchWorkloadStreamResponse)\n\tgo func() {\n\t\tfor {\n\t\t\tsnap, err := stream.Recv()\n\t\t\tch <- &watchWorkloadStreamResponse{\n\t\t\t\tworkloadInfoSnapshot: snap,\n\t\t\t\terr:                  err,\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tclose(ch)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase r, ok := <-ch:\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif r.err != nil {\n\t\t\t\treturn errcat.NoDaemonLogs.Newf(\"%v\", r.err)\n\t\t\t}\n\t\t\ts.printList(ctx, r.workloadInfoSnapshot.Workloads, stdout, formattedOutput)\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\t}\n\t}\n}\n\nfunc (s *listCommand) printList(ctx context.Context, workloads []*connector.WorkloadInfo, stdout io.Writer, formattedOut bool) {\n\tif len(workloads) == 0 {\n\t\tif formattedOut {\n\t\t\toutput.Object(ctx, []struct{}{}, false)\n\t\t} else {\n\t\t\tioutil.Println(stdout, \"No Workloads (Deployments, StatefulSets, ReplicaSets, or Rollouts)\")\n\t\t}\n\t\treturn\n\t}\n\n\tstate := func(workload *connector.WorkloadInfo) string {\n\t\tif iis, igs := workload.InterceptInfo, workload.IngestInfo; len(iis)+len(igs) > 0 {\n\t\t\treturn intercept.DescribeIntercepts(ctx, iis, igs, nil, s.debug)\n\t\t}\n\t\tif workload.NotInterceptableReason == \"Progressing\" {\n\t\t\treturn \"progressing...\"\n\t\t}\n\t\tif workload.AgentVersion != \"\" {\n\t\t\treturn \"ready to engage (traffic-agent already installed)\"\n\t\t}\n\t\tif workload.NotInterceptableReason != \"\" {\n\t\t\treturn \"unable to engage (traffic-agent not installed): \" + workload.NotInterceptableReason\n\t\t} else {\n\t\t\treturn \"ready to engage (traffic-agent not yet installed)\"\n\t\t}\n\t}\n\n\tif formattedOut {\n\t\toutput.Object(ctx, workloads, false)\n\t} else {\n\t\tincludeNs := false\n\t\tns := s.namespace\n\t\tfor _, dep := range workloads {\n\t\t\tdepNs := dep.Namespace\n\t\t\tif ns != \"\" && depNs != ns {\n\t\t\t\tincludeNs = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tns = depNs\n\t\t}\n\t\ttypeLen := 0\n\n\t\tnameLen := 0\n\t\tfor _, dep := range workloads {\n\t\t\tn := dep.WorkloadResourceType\n\t\t\tnl := len(n)\n\t\t\tif nl > typeLen {\n\t\t\t\ttypeLen = nl\n\t\t\t}\n\t\t\tn = dep.Name\n\t\t\tnl = len(n)\n\t\t\tif includeNs {\n\t\t\t\tnl += len(dep.Namespace) + 1\n\t\t\t}\n\t\t\tif nl > nameLen {\n\t\t\t\tnameLen = nl\n\t\t\t}\n\t\t}\n\t\tfor _, workload := range workloads {\n\t\t\tt := workload.WorkloadResourceType\n\t\t\tn := workload.Name\n\t\t\tif includeNs {\n\t\t\t\tn += \".\" + workload.Namespace\n\t\t\t}\n\t\t\tioutil.Printf(stdout, \"%-*s %-*s: %s\\n\", typeLen, strings.ToLower(t), nameLen, n, state(workload))\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/list_contexts.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype listContextsCommand struct {\n\trq *daemon.CobraRequest\n}\n\nfunc listContexts() *cobra.Command {\n\tlcc := &listContextsCommand{}\n\n\tcmd := &cobra.Command{\n\t\tUse:               \"list-contexts\",\n\t\tArgs:              cobra.NoArgs,\n\t\tShort:             \"Show all contexts\",\n\t\tRunE:              lcc.run,\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\tlcc.rq = daemon.InitRequest(cmd)\n\treturn cmd\n}\n\ntype kubeCtx struct {\n\t*api.Context `yaml:\",inline\"`\n\tCurrent      bool `json:\"current,omitempty\"`\n}\n\nfunc (lcc *listContextsCommand) run(cmd *cobra.Command, _ []string) error {\n\tconfig, err := lcc.rq.GetConfig(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx := cmd.Context()\n\tcm := make(map[string]kubeCtx, len(config.Contexts))\n\tfor n, c := range config.Contexts {\n\t\tcm[n] = kubeCtx{Context: c, Current: n == config.CurrentContext}\n\t}\n\n\tif output.WantsFormatted(cmd) {\n\t\toutput.Object(ctx, cm, false)\n\t} else {\n\t\tfor n, c := range cm {\n\t\t\tpfx := '-'\n\t\t\tif c.Current {\n\t\t\t\tpfx = '*'\n\t\t\t}\n\t\t\tioutil.Printf(output.Out(ctx), \"%c name: %s\\n  default namespace: %s\\n\", pfx, n, c.Namespace)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/list_namespaces.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n)\n\ntype listNamespacesCommand struct {\n\trq *daemon.CobraRequest\n}\n\nfunc listNamespaces() *cobra.Command {\n\tlnc := &listNamespacesCommand{}\n\n\tcmd := &cobra.Command{\n\t\tUse:               \"list-namespaces\",\n\t\tArgs:              cobra.NoArgs,\n\t\tShort:             \"Show all namespaces\",\n\t\tRunE:              lnc.run,\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\tlnc.rq = daemon.InitRequest(cmd)\n\treturn cmd\n}\n\nfunc (lnc *listNamespacesCommand) run(cmd *cobra.Command, _ []string) error {\n\tnss, err := lnc.rq.GetAllNamespaces(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx := cmd.Context()\n\tif output.WantsFormatted(cmd) {\n\t\toutput.Object(ctx, nss, false)\n\t} else {\n\t\tfor _, ns := range nss {\n\t\t\tfmt.Fprintln(output.Out(ctx), ns)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/loglevel.go",
    "content": "package cmd\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/protobuf/types/known/durationpb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n)\n\nconst defaultDuration = 30 * time.Minute\n\ntype logLevelCommand struct {\n\tduration   time.Duration\n\tlocalOnly  bool\n\tremoteOnly bool\n}\n\nfunc logLevelArg(cmd *cobra.Command, args []string) error {\n\tif len(args) != 1 {\n\t\treturn errors.New(\"accepts exactly one argument (the log level)\")\n\t}\n\t_, err := clog.ParseLevel(args[0])\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc loglevel() *cobra.Command {\n\tlls := logLevelCommand{}\n\tcmd := &cobra.Command{\n\t\tUse:       fmt.Sprintf(\"loglevel <%s>\", strings.Join(clog.LevelStrings, \",\")),\n\t\tArgs:      logLevelArg,\n\t\tShort:     \"Temporarily change the log-level of the traffic-manager, traffic-agent, and user and root daemons\",\n\t\tRunE:      lls.setTempLogLevel,\n\t\tValidArgs: clog.LevelStrings,\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Required,\n\t\t},\n\t}\n\tflags := cmd.Flags()\n\tflags.DurationVarP(&lls.duration, \"duration\", \"d\", defaultDuration, \"The time that the log-level will be in effect (0s means indefinitely)\")\n\tflags.BoolVarP(&lls.localOnly, \"local-only\", \"l\", false, \"Only affect the user and root daemons\")\n\tflags.BoolVarP(&lls.remoteOnly, \"remote-only\", \"r\", false, \"Only affect the traffic-manager and traffic-agents\")\n\treturn cmd\n}\n\nfunc (lls *logLevelCommand) setTempLogLevel(cmd *cobra.Command, args []string) error {\n\trq := &connector.LogLevelRequest{LogLevel: args[0], Duration: durationpb.New(lls.duration)}\n\tswitch {\n\tcase lls.localOnly && lls.remoteOnly:\n\t\treturn errcat.User.New(\"the local-only and remote-only options are mutually exclusive\")\n\tcase lls.localOnly:\n\t\trq.Scope = connector.LogLevelRequest_LOCAL_ONLY\n\tcase lls.remoteOnly:\n\t\trq.Scope = connector.LogLevelRequest_REMOTE_ONLY\n\t}\n\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\treturn err\n\t}\n\tdefer progress.Stop(cmd.Context())\n\tctx := cmd.Context()\n\tuserD := daemon.MustGetUserClient(ctx)\n\t_, err := userD.SetLogLevel(ctx, rq)\n\treturn grpc.FromGRPC(err)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/man-pages.go",
    "content": "package cmd\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc manPages() *cobra.Command {\n\tvar dir string\n\tcmd := &cobra.Command{\n\t\tUse:  \"man-pages\",\n\t\tArgs: cobra.NoArgs,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\treturn genMarkdown(cmd.Parent(), dir)\n\t\t},\n\t\tHidden:        true,\n\t\tSilenceErrors: true,\n\t\tSilenceUsage:  true,\n\t}\n\tflags := cmd.Flags()\n\tflags.StringVar(&dir, \"dir\", \"/tmp\", \"Directory to write the manual page to\")\n\treturn cmd\n}\n\nfunc genMarkdown(cmd *cobra.Command, dir string) error {\n\terr := os.MkdirAll(dir, 0o755)\n\tif err != nil {\n\t\treturn err\n\t}\n\tbuf := &bytes.Buffer{}\n\treturn genCommandMarkdown(cmd, dir, buf)\n}\n\nfunc entityEscape(s string, w *bytes.Buffer) {\n\tfor _, c := range s {\n\t\tswitch c {\n\t\tcase '&':\n\t\t\tw.WriteString(\"&amp;\")\n\t\tcase '<':\n\t\t\tw.WriteString(\"&lt;\")\n\t\tcase '>':\n\t\t\tw.WriteString(\"&gt;\")\n\t\tcase '\"':\n\t\t\tw.WriteString(\"&quot;\")\n\t\tdefault:\n\t\t\tw.WriteRune(c)\n\t\t}\n\t}\n}\n\n// GenMarkdownCustom creates custom markdown output.\nfunc genCommandMarkdown(cmd *cobra.Command, dir string, buf *bytes.Buffer) error {\n\tcmd.InitDefaultHelpFlag()\n\n\t// Create a font-matter header.\n\tbuf.WriteString(\"---\\ntitle: \")\n\tbuf.WriteString(cmd.CommandPath())\n\tbuf.WriteByte('\\n')\n\tif cmd.Short != \"\" {\n\t\tbuf.WriteString(\"description: \")\n\t\tentityEscape(cmd.Short, buf)\n\t\tbuf.WriteByte('\\n')\n\t}\n\tbuf.WriteString(\"hide_table_of_contents: true\\n---\\n\\n\")\n\tif cmd.Short != \"\" {\n\t\tentityEscape(cmd.Short, buf)\n\t\tbuf.WriteString(\"\\n\\n\")\n\t}\n\tif cmd.Long != \"\" {\n\t\tbuf.WriteString(\"## Synopsis:\\n\\n\")\n\t\tentityEscape(cmd.Long, buf)\n\t\tbuf.WriteString(\"\\n\\n\")\n\t}\n\n\tentityEscape(cmd.UsageString(), buf)\n\terr := os.WriteFile(fmt.Sprintf(\"%s/%s.md\", dir, strings.ReplaceAll(cmd.CommandPath(), \" \", \"_\")), buf.Bytes(), 0o644)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, c := range cmd.Commands() {\n\t\tif c.Hidden || c.Name() == \"help\" {\n\t\t\tcontinue\n\t\t}\n\t\tbuf.Reset()\n\t\terr = genCommandMarkdown(c, dir, buf)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/mcp.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/njayp/ophis\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc mcp() *cobra.Command {\n\treturn ophis.Command(&ophis.Config{\n\t\tSelectors: []ophis.Selector{\n\t\t\t{\n\t\t\t\tCmdSelector: ophis.AllowCmds(\"telepresence connect\"),\n\t\t\t\t// allow all local flags except kubeflags\n\t\t\t\tLocalFlagSelector: ophis.ExcludeFlags(\n\t\t\t\t\t\"as\",\n\t\t\t\t\t\"as-group\",\n\t\t\t\t\t\"as-uid\",\n\t\t\t\t\t\"cache-dir\",\n\t\t\t\t\t\"certificate-authority\",\n\t\t\t\t\t\"client-certificate\",\n\t\t\t\t\t\"client-key\",\n\t\t\t\t\t\"cluster\",\n\t\t\t\t\t\"context\",\n\t\t\t\t\t\"disable-compression\",\n\t\t\t\t\t\"insecure-skip-tls-verify\",\n\t\t\t\t\t\"kubeconfig\",\n\t\t\t\t\t\"request-timeout\",\n\t\t\t\t\t\"server\",\n\t\t\t\t\t\"tls-server-name\",\n\t\t\t\t\t\"token\",\n\t\t\t\t\t\"user\",\n\t\t\t\t),\n\t\t\t\tInheritedFlagSelector: ophis.NoFlags,\n\t\t\t},\n\t\t\t{\n\t\t\t\tCmdSelector: ophis.AllowCmds(\n\t\t\t\t\t\"telepresence quit\",\n\t\t\t\t\t\"telepresence status\",\n\t\t\t\t),\n\n\t\t\t\t// no local or global flags\n\t\t\t\tLocalFlagSelector:     ophis.NoFlags,\n\t\t\t\tInheritedFlagSelector: ophis.NoFlags,\n\t\t\t},\n\t\t\t{\n\t\t\t\tCmdSelector: ophis.AllowCmds(\n\t\t\t\t\t\"telepresence intercept\",\n\t\t\t\t\t\"telepresence ingest\",\n\t\t\t\t\t\"telepresence leave\",\n\t\t\t\t\t\"telepresence list\",\n\t\t\t\t\t\"telepresence wiretap\",\n\t\t\t\t\t\"telepresence replace\",\n\t\t\t\t),\n\n\t\t\t\t// allow local flags\n\t\t\t\t// allow global output flag for `--detailed-output` and `--output json` combo\n\t\t\t\tInheritedFlagSelector: ophis.AllowFlags(\"output\"),\n\t\t\t},\n\t\t},\n\t})\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/mcp_test.go",
    "content": "package cmd\n\nimport (\n\t\"testing\"\n\n\t\"github.com/njayp/ophis/test\"\n)\n\nfunc TestMCP(t *testing.T) {\n\tctx := WithSubCommands(t.Context())\n\tcmd := Telepresence(ctx, nil)\n\ttools := test.GetTools(t, cmd)\n\ttest.ToolNames(t, tools,\n\t\t\"telepresence_quit\",\n\t\t\"telepresence_status\",\n\t\t\"telepresence_connect\",\n\t\t\"telepresence_intercept\",\n\t\t\"telepresence_ingest\",\n\t\t\"telepresence_leave\",\n\t\t\"telepresence_list\",\n\t\t\"telepresence_wiretap\",\n\t\t\"telepresence_replace\",\n\t)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/quit.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n)\n\nfunc quit() *cobra.Command {\n\tquitDaemons := false\n\tcmd := &cobra.Command{\n\t\tUse:   \"quit\",\n\t\tArgs:  cobra.NoArgs,\n\t\tShort: \"Tell telepresence daemons to quit\",\n\t\tRunE: func(cmd *cobra.Command, _ []string) error {\n\t\t\tif quitDaemons {\n\t\t\t\tconnect.InitProgressWriter(cmd)\n\t\t\t\tconnect.Quit(cmd.Context())\n\t\t\t} else {\n\t\t\t\tcmd.Annotations = map[string]string{ann.UserDaemon: ann.Optional}\n\t\t\t\tif err := connect.InitCommand(cmd); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tconnect.Disconnect(cmd.Context())\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\tflags := cmd.Flags()\n\tflags.BoolVarP(&quitDaemons, \"stop-daemons\", \"s\", false, \"stop all local telepresence daemons\")\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/replace.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n)\n\nfunc replaceCmd() *cobra.Command {\n\tic := &intercept.Command{}\n\tcmd := &cobra.Command{\n\t\tUse:   \"replace [flags] <name> [-- [[docker run flags] <image name>] OR [<command>]] args...]\",\n\t\tArgs:  cobra.MinimumNArgs(1),\n\t\tShort: \"Replace a container\",\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session:           ann.Required,\n\t\t\tann.UpdateCheckFormat: ann.Tel2,\n\t\t},\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t\tRunE:              ic.RunReplace,\n\t\tValidArgsFunction: intercept.ValidArgs,\n\t}\n\tic.AddReplaceFlags(cmd)\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/revoke.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n)\n\nfunc revokeCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:  \"revoke <intercept_id>\",\n\t\tArgs: cobra.ExactArgs(1),\n\n\t\tShort: \"Revoke an intercept by intercept ID. The intercept ID must be in the format <session_id>:<intercept_name>\",\n\t\tLong: `Revoke an intercept by intercept ID. This is an administrative operation that\nrequires RBAC permissions to modify the \"traffic-manager\" configmap.`,\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Required,\n\t\t},\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif err := connect.InitCommand(cmd); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer progress.Stop(cmd.Context())\n\t\t\treturn revokeIntercept(cmd.Context(), strings.TrimSpace(args[0]))\n\t\t},\n\t}\n\treturn cmd\n}\n\nfunc revokeIntercept(ctx context.Context, interceptID string) error {\n\tif interceptID == \"\" {\n\t\treturn errcat.User.New(\"intercept_id cannot be empty\")\n\t}\n\n\tuserD := daemon.MustGetUserClient(ctx)\n\t_, err := userD.RevokeIntercept(ctx, &connector.RevokeInterceptRequest{\n\t\tInterceptId: interceptID,\n\t})\n\treturn grpc.FromGRPC(err)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/serve.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pkg/browser\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/sigctx\"\n)\n\ntype serveCommand struct {\n\tport uint16\n}\n\nfunc serveCmd() *cobra.Command {\n\tsc := &serveCommand{}\n\tcmd := &cobra.Command{\n\t\tUse:   \"serve <name of remote service>\",\n\t\tArgs:  cobra.ExactArgs(1),\n\t\tShort: \"Start the browser on a remote service\",\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Required,\n\t\t},\n\t\tSilenceUsage:  true,\n\t\tSilenceErrors: true,\n\t\tRunE:          sc.run,\n\t}\n\tsc.addFlags(cmd)\n\treturn cmd\n}\n\nfunc (sc *serveCommand) addFlags(cmd *cobra.Command) {\n\tfs := cmd.Flags()\n\tfs.Uint16VarP(&sc.port, \"port\", \"p\", 80, \"service port\")\n}\n\nfunc (sc *serveCommand) run(cmd *cobra.Command, args []string) error {\n\tsvc := args[0]\n\tif len(svc) == 0 {\n\t\treturn errcat.User.New(\"an empty string is never a valid service name\")\n\t}\n\terr := connect.InitCommand(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn sigctx.DoWithSignalHandler(cmd.Context(), func(ctx context.Context) error {\n\t\tuc := daemon.MustGetUserClient(ctx)\n\t\tip, err := uc.Lookup(ctx, svc)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif uc.Containerized() {\n\t\t\terr = sc.serveFromContainer(ctx, ip)\n\t\t} else {\n\t\t\terr = sc.serveFromHost(ctx, ip)\n\t\t}\n\t\treturn err\n\t})\n}\n\nconst (\n\tbrowserProgressID = \"Web-browser\"\n\tsocatProgressID   = \"Port-forward\"\n)\n\nfunc (sc *serveCommand) serveFromContainer(ctx context.Context, addr netip.Addr) error {\n\t// We can't reliably just map a service port (typically port 80) to localhost, so instead of doing\n\t// that, we create a random port and use that.\n\tps, err := ioutil.FreePortsTCP(1)\n\tif err != nil {\n\t\treturn err\n\t}\n\trndPort := ps[0].Port()\n\tap := netip.AddrPortFrom(addr, sc.port)\n\n\tprogress.Start(ctx, \"Serving web browser from container\")\n\tdefer progress.Stop(ctx)\n\n\tctx = progress.WithEventId(ctx, socatProgressID)\n\tprogress.Workingf(ctx, \"Starting port-forward %d:%s\", rndPort, ap)\n\tcni, cc, err := docker.Start(ctx, true, \"-p\", fmt.Sprintf(\"%d:%d\", rndPort, rndPort), \"--rm\",\n\t\t\"alpine/socat\", fmt.Sprintf(\"TCP-LISTEN:%d,fork\", rndPort), fmt.Sprintf(\"TCP:%s\", ap))\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\tprogress.Workingf(ctx, \"Started port-forward %d:%s\", rndPort, ap)\n\tgo func() {\n\t\t<-ctx.Done()\n\t\t_ = docker.StopContainer(context.WithoutCancel(ctx), cni.ID)\n\t}()\n\n\tproto := \"http\"\n\ton, err := url.Parse(fmt.Sprintf(\"%s://%s\", proto, net.JoinHostPort(\"localhost\", fmt.Sprintf(\"%d\", rndPort))))\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\tsc.openBrowser(progress.WithEventId(ctx, browserProgressID), on, wg)\n\terr = cc.Wait()\n\tif err != nil && ctx.Err() == nil {\n\t\treturn errcat.NoDaemonLogs.New(cc.Wait())\n\t}\n\twg.Wait()\n\tprogress.Donef(ctx, \"Stopped port-forward %d:%s\", rndPort, ap)\n\treturn nil\n}\n\nfunc (sc *serveCommand) serveFromHost(ctx context.Context, addr netip.Addr) error {\n\tproto := \"http\"\n\tap := netip.AddrPortFrom(addr, sc.port)\n\ton, err := url.Parse(fmt.Sprintf(\"%s://%s\", proto, ap))\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\n\tprogress.Start(ctx, \"Serving web browser from host\")\n\tdefer progress.Stop(ctx)\n\twg := &sync.WaitGroup{}\n\twg.Add(1)\n\tsc.openBrowser(progress.WithEventId(ctx, browserProgressID), on, wg)\n\t<-ctx.Done()\n\twg.Wait()\n\treturn nil\n}\n\nfunc (sc *serveCommand) openBrowser(ctx context.Context, on *url.URL, wg *sync.WaitGroup) {\n\tonStr := on.String()\n\tworking := progress.Workingf(ctx, \"Opening on %s\", onStr)\n\n\t// The browser might not open an existing session, in which case the OpenURL call will wait for the browser to close.\n\t// We don't want to wait here regardless, so we use a separate go-routine to start it and produce initial output on stdout/stderr.\n\t// This context is canceled either due to a quickly returning OpenURL (existing session) or after a second when\n\t// the context times out.\n\tctx, cancel := context.WithTimeout(ctx, time.Second)\n\tgo func() {\n\t\tdefer func() {\n\t\t\twg.Done()\n\t\t\tcancel()\n\t\t\tbrowser.Stderr = os.Stderr\n\t\t\tbrowser.Stdout = os.Stdout\n\t\t}()\n\t\tbrowser.Stderr = working.Pump(ctx, progress.EventStatusWarning)\n\t\tbrowser.Stdout = working.Pump(ctx, progress.EventStatusInfo)\n\t\terr := browser.OpenURL(onStr)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t}\n\t}()\n\t<-ctx.Done()\n\tprogress.Donef(ctx, \"Browser opened on %s\", onStr)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/status.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/spf13/cobra\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\tdaemonRpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype StatusInfo struct {\n\tRootDaemon     RootDaemonStatus     `json:\"root_daemon\"`\n\tUserDaemon     UserDaemonStatus     `json:\"user_daemon\"`\n\tTrafficManager TrafficManagerStatus `json:\"traffic_manager\"`\n}\n\ntype MultiConnectStatusInfo struct {\n\textendedInfo ioutil.WriterTos\n\tstatusInfos  []ioutil.WriterTos\n}\n\ntype SingleConnectStatusInfo struct {\n\textendedInfo ioutil.WriterTos\n\tstatusInfo   ioutil.WriterTos\n}\n\ntype RootDaemonStatus struct {\n\tManaged      bool             `json:\"managed,omitempty\"`\n\tRunning      bool             `json:\"running,omitempty\"`\n\tName         string           `json:\"name,omitempty\"`\n\tVersion      string           `json:\"version,omitempty\"`\n\tAPIVersion   int32            `json:\"api_version,omitempty\"`\n\tPortMappings []string         `json:\"port_mappings,omitempty\"`\n\tDNS          *client.DNSSnake `json:\"dns,omitempty\"`\n\t*client.RoutingSnake\n}\n\ntype UserDaemonStatus struct {\n\tRunning           bool                     `json:\"running,omitempty\"`\n\tInDocker          bool                     `json:\"in_docker,omitempty\"`\n\tName              string                   `json:\"name,omitempty\"`\n\tDaemonPort        int                      `json:\"daemon_port,omitempty\"`\n\tContainerNetwork  string                   `json:\"container_network,omitempty\"`\n\tHostname          string                   `json:\"hostname,omitempty\"`\n\tExposedPorts      []string                 `json:\"exposedPorts,omitempty\"`\n\tVersion           string                   `json:\"version,omitempty\"`\n\tExecutable        string                   `json:\"executable,omitempty\"`\n\tInstallID         string                   `json:\"install_id,omitempty\"`\n\tStatus            string                   `json:\"status,omitempty\"`\n\tError             string                   `json:\"error,omitempty\"`\n\tKubernetesServer  string                   `json:\"kubernetes_server,omitempty\"`\n\tKubernetesContext string                   `json:\"kubernetes_context,omitempty\"`\n\tNamespace         string                   `json:\"namespace,omitempty\"`\n\tManagerNamespace  string                   `json:\"manager_namespace,omitempty\"`\n\tMappedNamespaces  []string                 `json:\"mapped_namespaces,omitempty\"`\n\tIngests           []ConnectStatusIngest    `json:\"ingests,omitempty\"`\n\tIntercepts        []ConnectStatusIntercept `json:\"intercepts,omitempty\"`\n\tReplacements      []ConnectStatusIntercept `json:\"replacements,omitempty\"`\n\tWiretaps          []ConnectStatusIntercept `json:\"wiretaps,omitempty\"`\n\tversionName       string\n}\n\ntype ContainerizedDaemonStatus struct {\n\t*UserDaemonStatus\n\tPortMappings []string         `json:\"port_mappings,omitempty\"`\n\tDNS          *client.DNSSnake `json:\"dns,omitempty\"`\n\t*client.RoutingSnake\n}\n\ntype TrafficManagerStatus struct {\n\tName         string `json:\"name,omitempty\"`\n\tVersion      string `json:\"version,omitempty\"`\n\tTrafficAgent string `json:\"traffic_agent,omitempty\"`\n\textendedInfo ioutil.KeyValueProvider\n}\n\ntype ConnectStatusIngest struct {\n\tWorkload  string `json:\"workload,omitempty\"`\n\tContainer string `json:\"container,omitempty\"`\n\tMount     string `json:\"mount,omitempty\"`\n}\n\ntype ConnectStatusIntercept struct {\n\tName   string `json:\"name,omitempty\"`\n\tClient string `json:\"client,omitempty\"`\n}\n\nconst (\n\tmultiDaemonFlag = \"multi-daemon\"\n\tjsonFlag        = \"json\"\n)\n\nfunc statusCmd() *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:  \"status\",\n\t\tArgs: cobra.NoArgs,\n\n\t\tShort: \"Show connectivity status\",\n\t\tRunE:  run,\n\t\tAnnotations: map[string]string{\n\t\t\tann.UserDaemon: ann.Optional,\n\t\t},\n\t}\n\tflags := cmd.Flags()\n\tflags.Bool(multiDaemonFlag, false, \"always use multi-daemon output format, even if there's only one daemon connected\")\n\treturn cmd\n}\n\n// status will retrieve connectivity status from the daemon and print it on stdout.\nfunc run(cmd *cobra.Command, _ []string) error {\n\tvar mdErr daemon.MultipleDaemonsError\n\terr := connect.InitCommand(cmd)\n\tif err != nil {\n\t\tif !errors.As(err, &mdErr) {\n\t\t\treturn err\n\t\t}\n\t}\n\tctx := cmd.Context()\n\tdefer progress.Stop(ctx)\n\n\tvar sis []ioutil.WriterTos\n\tif len(mdErr) > 0 {\n\t\tsis = make([]ioutil.WriterTos, len(mdErr))\n\t\tfor i, info := range mdErr {\n\t\t\tudCtx, err := connect.ExistingDaemon(ctx, info)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tsis[i], err = getStatusInfo(udCtx, info)\n\t\t\t_ = daemon.MustGetUserClient(udCtx).Close()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t} else {\n\t\tsi, err := getStatusInfo(ctx, nil)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsis = []ioutil.WriterTos{si}\n\t}\n\n\tsx, err := GetStatusInfo(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmultiFormat := len(sis) > 1\n\tif !multiFormat {\n\t\tmultiFormat, _ = cmd.Flags().GetBool(multiDaemonFlag)\n\t}\n\tvar as ioutil.WriterTos\n\tif multiFormat {\n\t\tas = &MultiConnectStatusInfo{\n\t\t\textendedInfo: sx,\n\t\t\tstatusInfos:  sis,\n\t\t}\n\t} else {\n\t\tas = &SingleConnectStatusInfo{\n\t\t\textendedInfo: sx,\n\t\t\tstatusInfo:   sis[0],\n\t\t}\n\t}\n\n\tif output.WantsFormatted(cmd) {\n\t\toutput.Object(ctx, &as, true)\n\t} else {\n\t\t_, _ = ioutil.WriteAllTo(cmd.OutOrStdout(), as.WriterTos()...)\n\t}\n\treturn nil\n}\n\n// GetStatusInfo may return an extended struct\n//\n//nolint:gochecknoglobals // extension point\nvar GetStatusInfo = func(ctx context.Context) (ioutil.WriterTos, error) {\n\treturn nil, nil\n}\n\n// GetTrafficManagerStatusExtras may return an extended struct\n//\n//nolint:gochecknoglobals // extension point\nvar GetTrafficManagerStatusExtras = func(context.Context, daemon.UserClient) ioutil.KeyValueProvider {\n\treturn nil\n}\n\nfunc (s *StatusInfo) WriterTos() []io.WriterTo {\n\tif s.UserDaemon.InDocker {\n\t\treturn []io.WriterTo{\n\t\t\t&ContainerizedDaemonStatus{\n\t\t\t\tUserDaemonStatus: &s.UserDaemon,\n\t\t\t\tPortMappings:     s.RootDaemon.PortMappings,\n\t\t\t\tDNS:              s.RootDaemon.DNS,\n\t\t\t\tRoutingSnake:     s.RootDaemon.RoutingSnake,\n\t\t\t},\n\t\t\t&s.TrafficManager,\n\t\t}\n\t}\n\treturn []io.WriterTo{&s.UserDaemon, &s.RootDaemon, &s.TrafficManager}\n}\n\nfunc (s *StatusInfo) MarshalJSON() ([]byte, error) {\n\treturn json.Marshal(s.toMap())\n}\n\nfunc (s *StatusInfo) toMap() map[string]any {\n\tif s.UserDaemon.InDocker {\n\t\treturn map[string]any{\n\t\t\t\"daemon\": &ContainerizedDaemonStatus{\n\t\t\t\tUserDaemonStatus: &s.UserDaemon,\n\t\t\t\tDNS:              s.RootDaemon.DNS,\n\t\t\t\tRoutingSnake:     s.RootDaemon.RoutingSnake,\n\t\t\t},\n\t\t\t\"traffic_manager\": &s.TrafficManager,\n\t\t}\n\t}\n\treturn map[string]any{\n\t\t\"user_daemon\":     &s.UserDaemon,\n\t\t\"root_daemon\":     &s.RootDaemon,\n\t\t\"traffic_manager\": &s.TrafficManager,\n\t}\n}\n\nfunc setUserDaemonStatus(ctx context.Context, userD daemon.UserClient, di *daemon.Info, us *UserDaemonStatus) (*connector.ConnectInfo, error) {\n\tinstallID, err := client.InstallID(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tus.InstallID = installID\n\tus.Running = true\n\tus.Version = userD.Semver().String()\n\tus.versionName = userD.Name()\n\tus.Executable = userD.Executable()\n\tus.Name = userD.DaemonID().Name\n\n\tif userD.Containerized() {\n\t\tus.InDocker = true\n\t\tus.DaemonPort = userD.DaemonPort()\n\t\tif di != nil {\n\t\t\tus.Hostname = di.Hostname\n\t\t\tus.ExposedPorts = di.ExposedPorts\n\t\t}\n\t\tus.ContainerNetwork = userD.DaemonID().ContainerName()\n\t\tif us.versionName == \"\" {\n\t\t\tus.versionName = \"Daemon\"\n\t\t}\n\t} else if us.versionName == \"\" {\n\t\tus.versionName = \"User daemon\"\n\t}\n\n\tstatus, err := userD.Status(ctx, &empty.Empty{})\n\tif err != nil {\n\t\terr = grpc.FromGRPC(err)\n\t\tus.Status = \"Not connected\"\n\t\tus.Error = err.Error()\n\t\treturn nil, err\n\t}\n\tus.Status = \"Connected\"\n\tus.KubernetesServer = status.ClusterServer\n\tus.KubernetesContext = status.ClusterContext\n\tfor _, ig := range status.GetIngests() {\n\t\tus.Ingests = append(us.Ingests, ConnectStatusIngest{\n\t\t\tWorkload:  ig.Workload,\n\t\t\tContainer: ig.Container,\n\t\t\tMount:     ig.ClientMountPoint,\n\t\t})\n\t}\n\tfor _, icept := range status.GetIntercepts().GetIntercepts() {\n\t\tcis := ConnectStatusIntercept{\n\t\t\tName:   icept.Spec.Name,\n\t\t\tClient: icept.Spec.Client,\n\t\t}\n\t\tswitch {\n\t\tcase icept.Spec.NoDefaultPort:\n\t\t\tus.Replacements = append(us.Replacements, cis)\n\t\tcase icept.Spec.Wiretap:\n\t\t\tus.Wiretaps = append(us.Wiretaps, cis)\n\t\tdefault:\n\t\t\tus.Intercepts = append(us.Intercepts, cis)\n\t\t}\n\t}\n\tus.Namespace = status.Namespace\n\tus.ManagerNamespace = status.ManagerNamespace\n\tus.MappedNamespaces = status.MappedNamespaces\n\n\treturn status, nil\n}\n\nfunc getStatusInfo(ctx context.Context, di *daemon.Info) (*StatusInfo, error) {\n\twt := &StatusInfo{}\n\tvar rStatus *daemonRpc.DaemonStatus\n\tuserD := daemon.GetUserClient(ctx)\n\tif userD != nil {\n\t\tstatus, err := setUserDaemonStatus(ctx, userD, di, &wt.UserDaemon)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif mv := status.ManagerVersion; mv != nil {\n\t\t\ttm := &wt.TrafficManager\n\t\t\ttm.Name = mv.Name\n\t\t\ttm.Version = mv.Version\n\t\t\tif af, err := userD.AgentImageFQN(ctx, &empty.Empty{}); err == nil {\n\t\t\t\ttm.TrafficAgent = af.FQN\n\t\t\t}\n\t\t\ttm.extendedInfo = GetTrafficManagerStatusExtras(ctx, userD)\n\t\t}\n\t\trStatus = status.DaemonStatus\n\t} else {\n\t\tconn, err := daemon.DialRootDaemon(ctx, false)\n\t\tif err != nil {\n\t\t\treturn wt, nil\n\t\t}\n\t\tdefer conn.Close()\n\t\tif rStatus, err = daemonRpc.NewDaemonClient(conn).Status(ctx, &empty.Empty{}); err != nil {\n\t\t\treturn wt, err\n\t\t}\n\t}\n\n\tif rStatus == nil {\n\t\treturn wt, nil\n\t}\n\n\trs := &wt.RootDaemon\n\trs.Running = true\n\trs.Managed = rStatus.Managed\n\trs.Name = rStatus.Version.Name\n\tif rs.Name == \"\" {\n\t\trs.Name = \"Root Daemon\"\n\t}\n\trs.Version = rStatus.Version.Version\n\trs.APIVersion = rStatus.Version.ApiVersion\n\tif obc := rStatus.OutboundConfig; obc != nil {\n\t\trs.PortMappings = obc.PortMappings\n\t}\n\tif rootCfg, err := daemon.GetRootClientConfig(rStatus); err == nil {\n\t\tus := &wt.UserDaemon\n\t\trs.DNS = rootCfg.DNS().ToSnake()\n\t\trs.RoutingSnake = rootCfg.Routing().ToSnake()\n\t\tif us.InDocker {\n\t\t\tif len(rs.Subnets) == 0 {\n\t\t\t\t// No teleroute network is started when there are no subnets to route.\n\t\t\t\t// DNS is exposed on port 53 on the containerized daemon, so the\n\t\t\t\t// IP that it exposes on the default bridge can be used for DNS.\n\t\t\t\trs.DNS.LocalAddresses = []netip.AddrPort{netip.AddrPortFrom(userD.DaemonInfo().ContainerIP, 53)}\n\t\t\t\tus.ContainerNetwork = \"default bridge\"\n\t\t\t}\n\t\t}\n\t}\n\treturn wt, nil\n}\n\nfunc (s *SingleConnectStatusInfo) WriterTos() []io.WriterTo {\n\tvar wts []io.WriterTo\n\tif s.extendedInfo != nil {\n\t\twts = s.extendedInfo.WriterTos()\n\t}\n\twts = append(wts, s.statusInfo.WriterTos()...)\n\treturn wts\n}\n\nfunc (s *SingleConnectStatusInfo) MarshalJSON() ([]byte, error) {\n\tm, err := s.toMap()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn json.Marshal(m)\n}\n\nfunc (s *SingleConnectStatusInfo) toMap() (map[string]any, error) {\n\tm := make(map[string]any)\n\tif s.extendedInfo != nil {\n\t\tsx, err := json.Marshal(s.extendedInfo)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err = json.Unmarshal(sx, &m); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tsx, err := json.Marshal(s.statusInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err = json.Unmarshal(sx, &m); err != nil {\n\t\treturn nil, err\n\t}\n\treturn m, nil\n}\n\nfunc (s *MultiConnectStatusInfo) MarshalJSON() ([]byte, error) {\n\tm, err := s.toMap()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn json.Marshal(m)\n}\n\nfunc (s *MultiConnectStatusInfo) toMap() (map[string]any, error) {\n\tm := make(map[string]any)\n\tif s.extendedInfo != nil {\n\t\tsx, err := json.Marshal(s.extendedInfo)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err = json.Unmarshal(sx, &m); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tm[\"connections\"] = s.statusInfos\n\treturn m, nil\n}\n\nfunc (s *MultiConnectStatusInfo) WriterTos() []io.WriterTo {\n\tvar wts []io.WriterTo\n\tif s.extendedInfo != nil {\n\t\twts = s.extendedInfo.WriterTos()\n\t}\n\tfor _, v := range s.statusInfos {\n\t\twts = append(wts, v.WriterTos()...)\n\t}\n\treturn wts\n}\n\nfunc (cs *ContainerizedDaemonStatus) WriteTo(out io.Writer) (int64, error) {\n\tn := 0\n\tif cs.Running {\n\t\tn += ioutil.Printf(out, \"%s %s: Running\\n\", cs.versionName, cs.Name)\n\t\tkvf := ioutil.DefaultKeyValueFormatter()\n\t\tkvf.Prefix = \"  \"\n\t\tkvf.Indent = \"  \"\n\t\tcs.print(kvf)\n\t\tif len(cs.PortMappings) > 0 {\n\t\t\tprintPortMappings(kvf, cs.PortMappings)\n\t\t}\n\t\tif cs.DNS != nil {\n\t\t\tprintDNS(kvf, cs.DNS)\n\t\t}\n\t\tif cs.RoutingSnake != nil {\n\t\t\tprintRouting(kvf, cs.RoutingSnake)\n\t\t}\n\t\tn += kvf.Println(out)\n\t} else {\n\t\tn += ioutil.Println(out, \"Daemon: Not running\")\n\t}\n\treturn int64(n), nil\n}\n\nfunc (ds *RootDaemonStatus) WriteTo(out io.Writer) (int64, error) {\n\tn := 0\n\tif ds.Running {\n\t\tmgd := \"\"\n\t\tif ds.Managed {\n\t\t\tmgd = \" (managed)\"\n\t\t}\n\t\tn += ioutil.Printf(out, \"%s%s: Running\\n\", ds.Name, mgd)\n\t\tkvf := ioutil.DefaultKeyValueFormatter()\n\t\tkvf.Prefix = \"  \"\n\t\tkvf.Indent = \"  \"\n\t\tkvf.Add(\"Version\", ds.Version)\n\t\tif len(ds.PortMappings) > 0 {\n\t\t\tprintPortMappings(kvf, ds.PortMappings)\n\t\t}\n\t\tif ds.DNS != nil {\n\t\t\tprintDNS(kvf, ds.DNS)\n\t\t}\n\t\tif ds.RoutingSnake != nil {\n\t\t\tprintRouting(kvf, ds.RoutingSnake)\n\t\t}\n\t\tn += kvf.Println(out)\n\t} else {\n\t\tn += ioutil.Println(out, \"OSS Root Daemon: Not running\")\n\t}\n\treturn int64(n), nil\n}\n\nfunc printPortMappings(kvf *ioutil.KeyValueFormatter, pms []string) {\n\tpmKvf := ioutil.DefaultKeyValueFormatter()\n\tfor _, pm := range pms {\n\t\tix := strings.LastIndexByte(pm, ':')\n\t\tif ix < 0 {\n\t\t\tcontinue\n\t\t}\n\t\tpmKvf.Add(pm[:ix], pm[ix+1:])\n\t}\n\tkvf.Add(\"Port Mappings\", \"\\n\"+pmKvf.String())\n}\n\nfunc printDNS(kvf *ioutil.KeyValueFormatter, d *client.DNSSnake) {\n\tdnsKvf := ioutil.DefaultKeyValueFormatter()\n\tif d.Error != \"\" {\n\t\tdnsKvf.Add(\"Error\", d.Error)\n\t}\n\tif len(d.LocalAddresses) > 0 {\n\t\tdnsKvf.Add(\"Local addresses\", fmt.Sprintf(\"%s\", d.LocalAddresses))\n\t}\n\tif d.VIFAddress.IsValid() {\n\t\tdnsKvf.Add(\"VIF Address\", d.VIFAddress.String())\n\t}\n\tdnsKvf.Add(\"Exclude suffixes\", fmt.Sprintf(\"%v\", d.ExcludeSuffixes))\n\tdnsKvf.Add(\"Include suffixes\", fmt.Sprintf(\"%v\", d.IncludeSuffixes))\n\tif len(d.Excludes) > 0 {\n\t\tdnsKvf.Add(\"Excludes\", fmt.Sprintf(\"%v\", d.Excludes))\n\t}\n\tif len(d.Mappings) > 0 {\n\t\tmappingsKvf := ioutil.DefaultKeyValueFormatter()\n\t\tfor i := range d.Mappings {\n\t\t\tmappingsKvf.Add(d.Mappings[i].Name, d.Mappings[i].AliasFor)\n\t\t}\n\t\tdnsKvf.Add(\"Mappings\", \"\\n\"+mappingsKvf.String())\n\t}\n\tdnsKvf.Add(\"Timeout\", fmt.Sprintf(\"%v\", d.LookupTimeout))\n\tkvf.Add(\"DNS\", \"\\n\"+dnsKvf.String())\n}\n\nfunc printRouting(kvf *ioutil.KeyValueFormatter, r *client.RoutingSnake) {\n\tprintSubnets := func(title string, subnets []netip.Prefix) {\n\t\tif len(subnets) == 0 {\n\t\t\treturn\n\t\t}\n\t\tout := &strings.Builder{}\n\t\tioutil.Printf(out, \"(%d subnets)\", len(subnets))\n\t\tfor _, subnet := range subnets {\n\t\t\tioutil.Printf(out, \"\\n- %s\", subnet)\n\t\t}\n\t\tkvf.Add(title, out.String())\n\t}\n\tprintSubnets(\"Subnets\", r.Subnets)\n\tprintSubnets(\"Also Proxy\", r.AlsoProxy)\n\tprintSubnets(\"Never Proxy\", r.NeverProxy)\n\tprintSubnets(\"Allow conflicts for\", r.AllowConflicting)\n}\n\nfunc (cs *UserDaemonStatus) WriteTo(out io.Writer) (int64, error) {\n\tn := 0\n\tif cs.Running {\n\t\tn += ioutil.Printf(out, \"%s: Running\\n\", cs.versionName)\n\t\tkvf := ioutil.DefaultKeyValueFormatter()\n\t\tkvf.Prefix = \"  \"\n\t\tkvf.Indent = \"  \"\n\t\tcs.print(kvf)\n\t\tn += kvf.Println(out)\n\t} else {\n\t\tn += ioutil.Println(out, \"OSS User Daemon: Not running\")\n\t}\n\treturn int64(n), nil\n}\n\nfunc (cs *UserDaemonStatus) print(kvf *ioutil.KeyValueFormatter) {\n\tkvf.Add(\"Version\", cs.Version)\n\tkvf.Add(\"Executable\", cs.Executable)\n\tkvf.Add(\"Install ID\", cs.InstallID)\n\tkvf.Add(\"Status\", cs.Status)\n\tif cs.Error != \"\" {\n\t\tkvf.Add(\"Error\", cs.Error)\n\t}\n\tkvf.Add(\"Kubernetes server\", cs.KubernetesServer)\n\tkvf.Add(\"Kubernetes context\", cs.KubernetesContext)\n\tif cs.ContainerNetwork != \"\" {\n\t\tkvf.Add(\"Container network\", cs.ContainerNetwork)\n\t}\n\tkvf.Add(\"Namespace\", cs.Namespace)\n\tkvf.Add(\"Manager namespace\", cs.ManagerNamespace)\n\tif len(cs.MappedNamespaces) > 0 {\n\t\tkvf.Add(\"Mapped namespaces\", fmt.Sprintf(\"%v\", cs.MappedNamespaces))\n\t}\n\tif cs.Hostname != \"\" {\n\t\tkvf.Add(\"Hostname\", cs.Hostname)\n\t}\n\tif len(cs.ExposedPorts) > 0 {\n\t\tkvf.Add(\"Exposed ports\", fmt.Sprintf(\"%v\", cs.ExposedPorts))\n\t}\n\tif il := len(cs.Ingests); il > 0 {\n\t\tout := &strings.Builder{}\n\t\tioutil.Printf(out, \"%d total\\n\", il)\n\t\tfor _, ingest := range cs.Ingests {\n\t\t\tioutil.Printf(out, \"  %s/%s\\n\", ingest.Workload, ingest.Container)\n\t\t}\n\t\tkvf.Add(\"Ingests\", out.String())\n\t}\n\taddInterceptGroup := func(name string, intercepts []ConnectStatusIntercept) {\n\t\tif il := len(intercepts); il > 0 {\n\t\t\tout := &strings.Builder{}\n\t\t\tioutil.Printf(out, \"%d total\\n\", il)\n\t\t\tsubKvf := ioutil.DefaultKeyValueFormatter()\n\t\t\tsubKvf.Indent = \"  \"\n\t\t\tfor _, intercept := range intercepts {\n\t\t\t\tsubKvf.Add(intercept.Name, intercept.Client)\n\t\t\t}\n\t\t\tsubKvf.Println(out)\n\t\t\tkvf.Add(name, out.String())\n\t\t}\n\t}\n\taddInterceptGroup(\"Replacements\", cs.Replacements)\n\taddInterceptGroup(\"Intercepts\", cs.Intercepts)\n\taddInterceptGroup(\"Wiretaps\", cs.Wiretaps)\n}\n\nfunc (ts *TrafficManagerStatus) MarshalJSON() ([]byte, error) {\n\tm, err := ts.toMap()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn json.Marshal(m)\n}\n\nfunc (ts *TrafficManagerStatus) toMap() (map[string]any, error) {\n\tm := make(map[string]any)\n\tif ts.extendedInfo != nil {\n\t\tsx, err := json.Marshal(ts.extendedInfo)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif err = json.Unmarshal(sx, &m); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tm[\"name\"] = ts.Name\n\tm[\"traffic_agent\"] = ts.TrafficAgent\n\tm[\"version\"] = ts.Version\n\treturn m, nil\n}\n\nfunc (ts *TrafficManagerStatus) WriteTo(out io.Writer) (int64, error) {\n\tn := 0\n\tif ts.Name != \"\" {\n\t\tn += ioutil.Printf(out, \"%s: Connected\\n\", ts.Name)\n\t\tkvf := ioutil.DefaultKeyValueFormatter()\n\t\tkvf.Prefix = \"  \"\n\t\tkvf.Indent = \"  \"\n\t\tkvf.Add(\"Version\", ts.Version)\n\t\tif ts.TrafficAgent != \"\" {\n\t\t\tkvf.Add(\"Traffic Agent\", ts.TrafficAgent)\n\t\t}\n\t\tif ts.extendedInfo != nil {\n\t\t\tts.extendedInfo.AddTo(kvf)\n\t\t}\n\t\tn += kvf.Println(out)\n\t} else {\n\t\tn += ioutil.Println(out, \"Traffic Manager: Not connected\")\n\t}\n\treturn int64(n), nil\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/telepresence.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cache\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker/kubeauth\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/rootd\"\n\tuserDaemon \"github.com/telepresenceio/telepresence/v2/pkg/client/userd/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\n// Telepresence returns the top level \"telepresence\" CLI command.\nfunc Telepresence(ctx context.Context, args []string) *cobra.Command {\n\tuseMarkdown := len(args) > 0 && args[0] == \"man-pages\"\n\tlongHelp := helpPlain\n\tif useMarkdown {\n\t\tos.Setenv(\"KUBECACHEDIR\", \"$HOME/.kube/cache\")\n\t\tlongHelp = helpMarkdown\n\t}\n\trootCmd := &cobra.Command{\n\t\tUse:   \"telepresence\",\n\t\tArgs:  OnlySubcommands,\n\t\tShort: \"Connect your workstation to a Kubernetes cluster\",\n\t\tLong:  longHelp,\n\t\tPersistentPreRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif err := output.SetFormat(cmd, args); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn ensureCacheVersion(cmd, args)\n\t\t},\n\t\tRunE:              RunSubcommands,\n\t\tSilenceErrors:     true, // main() will handle it after .ExecuteContext() returns\n\t\tSilenceUsage:      true, // our FlagErrorFunc will handle it\n\t\tTraverseChildren:  true,\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n\trootCmd.SetArgs(args)\n\trootCmd.SetContext(ctx)\n\tAddSubCommands(rootCmd, useMarkdown)\n\trootCmd.SetFlagErrorFunc(func(_ *cobra.Command, err error) error {\n\t\treturn errcat.User.New(err)\n\t})\n\treturn rootCmd\n}\n\n// TelepresenceDaemon returns the top level \"telepresence\" CLI limited to the subcommands kubeauthd, userd, and rootd.\nfunc TelepresenceDaemon(ctx context.Context, args []string) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:  \"telepresence\",\n\t\tArgs: OnlySubcommands,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcmd.SetOut(cmd.ErrOrStderr())\n\t\t\treturn nil\n\t\t},\n\t\tSilenceErrors: true, // main() will handle it after .ExecuteContext() returns\n\t\tSilenceUsage:  true, // our FlagErrorFunc will handle it\n\t}\n\tcmd.SetArgs(args)\n\tcmd.SetContext(ctx)\n\tAddSubCommands(cmd, false)\n\treturn cmd\n}\n\nfunc setContext(cmd *cobra.Command, ctx context.Context) {\n\tcmd.SetContext(ctx)\n\tfor _, c := range cmd.Commands() {\n\t\tsetContext(c, ctx)\n\t}\n}\n\n// AddSubCommands adds subcommands to the given command, including the default help, the commands in the\n// CommandGroups found in the given command's context, and the completion command. It also replaces\n// the standard usage template with a custom template.\nfunc AddSubCommands(cmd *cobra.Command, markdown bool) {\n\tctx := cmd.Context()\n\tcommands := getSubCommands(cmd)\n\tfor _, command := range commands {\n\t\tif ac := command.Args; ac != nil {\n\t\t\t// Ensure that args errors don't advice the user to look in log files\n\t\t\tcommand.Args = argsCheck(ac)\n\t\t}\n\t\tsetContext(command, ctx)\n\t}\n\tcmd.AddCommand(commands...)\n\tif client.ProcessName() != client.RootDaemonName {\n\t\tcmd.PersistentFlags().AddFlagSet(global.Flags(ctx, false, markdown))\n\t\taddCompletion(cmd, markdown)\n\t\taddUsageTemplate(cmd, markdown)\n\t\t_ = cmd.RegisterFlagCompletionFunc(\"context\", autocompleteContext)\n\t}\n}\n\n// RunSubcommands is for use as a cobra.interceptCmd.RunE for commands that don't do anything themselves\n// but have subcommands.  In such cases, it is important to set RunE even though there's nothing to\n// run, because otherwise cobra will treat that as \"success\", and it shouldn't be \"success\" if the\n// user typos a command and types something invalid.\nfunc RunSubcommands(cmd *cobra.Command, args []string) error {\n\t// determine if --help was explicitly asked for\n\tvar usedHelpFlag bool\n\tfor _, arg := range args {\n\t\tif arg == \"--help\" || arg == \"-h\" {\n\t\t\tusedHelpFlag = true\n\t\t}\n\t}\n\t// If there are no args or --help was used, then it's not a legacy\n\t// Telepresence command so we return the help text\n\tif len(args) == 0 || usedHelpFlag {\n\t\tcmd.HelpFunc()(cmd, args)\n\t\treturn nil\n\t}\n\treturn nil\n}\n\n// OnlySubcommands is a cobra.PositionalArgs that is similar to cobra.NoArgs, but prints a better\n// error message.\nfunc OnlySubcommands(cmd *cobra.Command, args []string) error {\n\tif len(args) == 0 {\n\t\treturn nil\n\t}\n\tif args[0] == \"-h\" {\n\t\treturn nil\n\t}\n\terr := fmt.Errorf(\"invalid subcommand %q\", args[0])\n\tif cmd.SuggestionsMinimumDistance <= 0 {\n\t\tcmd.SuggestionsMinimumDistance = 2\n\t}\n\tif suggestions := cmd.SuggestionsFor(args[0]); len(suggestions) > 0 {\n\t\terr = fmt.Errorf(\"%w\\nDid you mean one of these?\\n\\t%s\", err, strings.Join(suggestions, \"\\n\\t\"))\n\t}\n\treturn cmd.FlagErrorFunc()(cmd, err)\n}\n\nfunc WithSubCommands(ctx context.Context) context.Context {\n\treturn MergeSubCommands(ctx,\n\t\tcomposeCmd(),\n\t\tconfigCmd(),\n\t\tconnectCmd(),\n\t\tcurlCmd(),\n\t\tdockerRunCmd(),\n\t\tgatherLogs(),\n\t\tgenYAML(),\n\t\thelmCmd(),\n\t\tingestCmd(),\n\t\tinterceptCmd(),\n\t\tkubeauthCmd(),\n\t\tleaveCmd(),\n\t\tlist(),\n\t\tlistContexts(),\n\t\trevokeCmd(),\n\t\tlistNamespaces(),\n\t\tloglevel(),\n\t\tmanPages(),\n\t\tmcp(),\n\t\tquit(),\n\t\treplaceCmd(),\n\t\tserveCmd(),\n\t\tstatusCmd(),\n\t\tuninstall(),\n\t\tversionCmd(),\n\t\twiretapCmd(),\n\t)\n}\n\nfunc WithDaemonSubCommands(ctx context.Context) context.Context {\n\treturn MergeSubCommands(ctx, kubeauth.Command(ctx), userDaemon.Command(ctx), rootd.Command(ctx))\n}\n\ntype subCommandsKey struct{}\n\nfunc MergeSubCommands(ctx context.Context, commands ...*cobra.Command) context.Context {\n\tif ecs, ok := ctx.Value(subCommandsKey{}).(*[]*cobra.Command); ok {\n\t\t*ecs = mergeCommands(*ecs, commands)\n\t} else {\n\t\tctx = context.WithValue(ctx, subCommandsKey{}, &commands)\n\t}\n\treturn ctx\n}\n\nfunc getSubCommands(cmd *cobra.Command) []*cobra.Command {\n\tif gs, ok := cmd.Context().Value(subCommandsKey{}).(*[]*cobra.Command); ok {\n\t\treturn *gs\n\t}\n\treturn nil\n}\n\n// mergeCommands merges the command slice b into a, replacing commands using the same name\n// and returns the resulting slice.\nfunc mergeCommands(a, b []*cobra.Command) []*cobra.Command {\n\tac := make(map[string]*cobra.Command, len(a)+len(b))\n\tfor _, c := range a {\n\t\tac[c.Name()] = c\n\t}\n\tfor _, c := range b {\n\t\tac[c.Name()] = c\n\t}\n\treturn maps.ToSortedSlice(ac)\n}\n\n// argsCheck wraps an PositionalArgs checker in a function that wraps a potential error\n// using errcat.User.\nfunc argsCheck(f cobra.PositionalArgs) cobra.PositionalArgs {\n\treturn func(cmd *cobra.Command, args []string) error {\n\t\tif err := f(cmd, args); err != nil {\n\t\t\treturn errcat.User.New(err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\nfunc autocompleteContext(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tctx := cmd.Context()\n\tclog.Debugf(ctx, \"context completion: %q\", toComplete)\n\tcfg, err := daemon.GetKubeStartingConfig(cmd)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"GetKubeStartingConfig: %v\", err)\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tcxl := cfg.Contexts\n\tnss := make([]string, len(cxl))\n\ti := 0\n\tfor n := range cxl {\n\t\tnss[i] = n\n\t\ti++\n\t}\n\treturn nss, cobra.ShellCompDirectiveNoFileComp\n}\n\ntype versionFile struct {\n\tVersion semver.Version `json:\"version\"`\n}\n\nfunc ensureCacheVersion(cmd *cobra.Command, _ []string) error {\n\tctx := cmd.Context()\n\tvar vf versionFile\n\tmajorMinorMatch := false\n\tif err := cache.LoadFromUserCache(ctx, &vf, \"version.json\"); err == nil {\n\t\tmajorMinorMatch = vf.Version.Major == version.Structured.Major && vf.Version.Minor == version.Structured.Minor\n\t\tif majorMinorMatch {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// Quit all daemons (best effort, ignore errors).\n\tconnect.Quit(ctx)\n\n\t// Clear cache except logs.\n\tcacheDir := filelocation.AppUserCacheDir(ctx)\n\tentries, err := os.ReadDir(cacheDir)\n\tif err != nil && !errors.Is(err, fs.ErrNotExist) {\n\t\treturn err\n\t}\n\tfor _, entry := range entries {\n\t\tif entry.Name() == \"logs\" {\n\t\t\tcontinue\n\t\t}\n\t\t_ = os.RemoveAll(filepath.Join(cacheDir, entry.Name()))\n\t}\n\n\treturn cache.SaveToUserCache(ctx, &versionFile{Version: version.Structured}, \"version.json\", cache.Public)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/license",
    "content": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/cli.log",
    "content": "2022-02-12 15:02:29.6927 info    Logging at this level \"debug\""
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/connector-20210916T130347.log",
    "content": "2021-09-16 13:03:19.0501 info    Logging at this level \"info\"\n2021-09-16 13:03:19.0865 info    ---\n2021-09-16 13:03:19.0865 info    Telepresence Connector v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:03:19.0865 info    PID is 42030\n2021-09-16 13:03:19.0865 info    \n2021-09-16 13:03:19.0867 info    connector/server-grpc : gRPC server started\n2021-09-16 13:03:19.2546 info    connector/background-init : Connecting to daemon...\n2021-09-16 13:03:19.2556 info    connector/background-init : Connecting to k8s cluster...\n2021-09-16 13:03:19.4222 info    connector/background-init : Server version v1.17.17+k3s1\n2021-09-16 13:03:19.4224 info    connector/background-init : Context: default\n2021-09-16 13:03:19.4225 info    connector/background-init : Server: https://35.224.222.254\n2021-09-16 13:03:19.4226 info    connector/background-init : Connected to context default (https://35.224.222.254)\n2021/09/16 13:03:19 Patching synced Namespace 55b19f40-65d2-4a0a-b61e-3009a28881d4\n2021-09-16 13:03:19.9566 info    connector/background-init : Connecting to traffic manager...\n2021-09-16 13:03:19.9567 info    connector/background-init : Waiting for TrafficManager to connect\n2021-09-16 13:03:20.1110 info    connector/background-manager : Existing Traffic Manager found, upgrading to v2.4.3-98-g0585bbd1-1631811285...\n2021-09-16 13:03:37.1864 info    connector/server-grpc/conn=1/Uninstall-3 : In Deployment echo-easy, reveal hidden port \"tm-http\" in container echo-easy by restoring its origina name \"http\", and remove traffic-agent container with image docker.io/datawire/ambassador-telepresence-agent:1.11.1-rc.0.\n2021-09-16 13:03:39.7361 info    connector/server-grpc/conn=1/Uninstall-3 : Uninstalling Traffic Manager\n2021-09-16 13:03:43.0277 info    connector/background-manager:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:43.0277 info    connector/background-systema:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:43.0279 info    connector:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:43.0281 info    connector/background-k8swatch:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:43.2349 info    connector/server-grpc/conn=2:shutdown_logger : shutting down (gracefully)...\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/connector-20210916T130356.log",
    "content": "2021-09-16 13:03:47.7084 info    Logging at this level \"info\"\n2021-09-16 13:03:47.7451 info    ---\n2021-09-16 13:03:47.7451 info    Telepresence Connector v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:03:47.7452 info    PID is 42128\n2021-09-16 13:03:47.7452 info    \n2021-09-16 13:03:47.7453 info    connector/server-grpc : gRPC server started\n2021-09-16 13:03:54.6001 info    connector/background-systema:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:54.6002 info    connector:shutdown_logger : shutting down (gracefully)...\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/connector-20210916T130624.log",
    "content": "2021-09-16 13:03:56.0769 info    Logging at this level \"info\"\n2021-09-16 13:03:56.1107 info    ---\n2021-09-16 13:03:56.1108 info    Telepresence Connector v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:03:56.1108 info    PID is 42196\n2021-09-16 13:03:56.1108 info    \n2021-09-16 13:03:56.1109 info    connector/server-grpc : gRPC server started\n2021-09-16 13:04:02.6361 info    connector/background-init : Connecting to daemon...\n2021-09-16 13:04:02.6368 info    connector/background-init : Connecting to k8s cluster...\n2021-09-16 13:04:02.8130 info    connector/background-init : Server version v1.17.17+k3s1\n2021-09-16 13:04:02.8131 info    connector/background-init : Context: default\n2021-09-16 13:04:02.8131 info    connector/background-init : Server: https://35.224.222.254\n2021-09-16 13:04:02.8131 info    connector/background-init : Connected to context default (https://35.224.222.254)\n2021-09-16 13:04:02.8764 info    connector/background-init : Connecting to traffic manager...\n2021-09-16 13:04:02.8766 info    connector/background-init : Waiting for TrafficManager to connect\n2021/09/16 13:04:02 Patching synced Namespace 55b19f40-65d2-4a0a-b61e-3009a28881d4\n2021-09-16 13:04:04.0645 info    connector/background-manager : No existing Traffic Manager found, installing v2.4.3-98-g0585bbd1-1631811285...\n2021-09-16 13:05:28.7671 info    connector/background-k8swatch:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:05:28.7672 info    connector/background-systema:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:05:28.7672 info    connector:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:05:28.7673 info    connector/background-manager:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:05:28.9704 info    connector/server-grpc/conn=3:shutdown_logger : shutting down (gracefully)...\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/connector-20210916T130643.log",
    "content": "2021-09-16 13:06:24.4082 info    Logging at this level \"info\"\n2021-09-16 13:06:24.4460 info    ---\n2021-09-16 13:06:24.4461 info    Telepresence Connector v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:06:24.4461 info    PID is 42655\n2021-09-16 13:06:24.4461 info    \n2021-09-16 13:06:24.4462 info    connector/server-grpc : gRPC server started\n2021-09-16 13:06:24.6166 info    connector/background-init : Connecting to daemon...\n2021-09-16 13:06:24.6179 info    connector/background-init : Connecting to k8s cluster...\n2021-09-16 13:06:24.8007 info    connector/background-init : Server version v1.17.17+k3s1\n2021-09-16 13:06:24.8008 info    connector/background-init : Context: default\n2021-09-16 13:06:24.8008 info    connector/background-init : Server: https://35.224.222.254\n2021-09-16 13:06:24.8009 info    connector/background-init : Connected to context default (https://35.224.222.254)\n2021-09-16 13:06:24.8772 info    connector/background-init : Connecting to traffic manager...\n2021-09-16 13:06:24.8774 info    connector/background-init : Waiting for TrafficManager to connect\n2021/09/16 13:06:24 Patching synced Namespace 55b19f40-65d2-4a0a-b61e-3009a28881d4\n2021-09-16 13:06:25.1442 info    connector/background-manager : Existing Traffic Manager not owned by cli or does not need upgrade, will not modify\n2021-09-16 13:06:26.1202 info    connector/server-grpc/conn=1/Uninstall-3 : Uninstalling Traffic Manager\n2021-09-16 13:06:29.1287 info    connector/background-k8swatch:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:06:29.1289 info    connector/background-manager:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:06:29.1289 info    connector:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:06:29.1291 info    connector/background-systema:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:06:29.3357 info    connector/server-grpc/conn=2:shutdown_logger : shutting down (gracefully)...\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/connector.log",
    "content": "2021-09-16 13:06:43.4849 info    Logging at this level \"info\"\n2021-09-16 13:06:43.5211 info    ---\n2021-09-16 13:06:43.5211 info    Telepresence Connector v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:06:43.5211 info    PID is 42749\n2021-09-16 13:06:43.5212 info    \n2021-09-16 13:06:43.5213 info    connector/server-grpc : gRPC server started\n2021-09-16 13:06:43.6978 info    connector/background-init : Connecting to daemon...\n2021-09-16 13:06:43.6992 info    connector/background-init : Connecting to k8s cluster...\n2021-09-16 13:06:43.9374 info    connector/background-init : Server version v1.17.17+k3s1\n2021-09-16 13:06:43.9375 info    connector/background-init : Context: default\n2021-09-16 13:06:43.9375 info    connector/background-init : Server: https://35.224.222.254\n2021-09-16 13:06:43.9375 info    connector/background-init : Connected to context default (https://35.224.222.254)\n2021-09-16 13:06:44.0110 info    connector/background-init : Connecting to traffic manager...\n2021-09-16 13:06:44.0112 info    connector/background-init : Waiting for TrafficManager to connect\n2021/09/16 13:06:44 Patching synced Namespace 55b19f40-65d2-4a0a-b61e-3009a28881d4\n2021-09-16 13:06:46.4289 info    connector/background-manager : No existing Traffic Manager found, installing v2.4.3-98-g0585bbd1-1631811285...\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/daemon-20210916T130318.log",
    "content": "2021/09/15 16:54:57.4146 debug    : Logging at this level \"debug\"\n2021/09/15 16:54:57.4510 info     : ---\n2021/09/15 16:54:57.4510 info     : Telepresence daemon v2.4.2 (api v3) starting...\n2021/09/15 16:54:57.4510 info     : PID is 21428\n2021/09/15 16:54:57.4510 info     : \n2021/09/15 16:54:57.4512 debug   daemon/server-grpc : gRPC server starting\n2021/09/15 16:54:57.4512 debug   daemon/server-router/TUN reader : Waiting until manager gRPC is configured\n2021/09/15 16:54:57.4513 debug   daemon/server-router/MGR stream : Waiting until manager gRPC is configured\n2021/09/15 16:54:57.4516 info    daemon/server-grpc : gRPC server started\n2021/09/15 16:55:05.9771 info    daemon/server-grpc/conn=2 : Adding also-proxy subnet 1.2.3.4/32\n2021/09/15 16:55:06.0502 info    daemon/watch-cluster-info : Adding service subnet 10.43.0.0/16\n2021/09/15 16:55:06.0503 info    daemon/watch-cluster-info : Adding pod subnet 10.42.0.0/24\n2021/09/15 16:55:06.0514 debug   daemon/server-router/TUN reader : TUN read loop starting\n2021/09/15 16:55:06.0517 debug   daemon/server-router/MGR stream : MGR read loop starting\n2021/09/15 16:55:06.0524 info    daemon/server-dns : Generated new /etc/resolver/telepresence.local\n2021/09/15 16:55:06.0587 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"21436\"\n2021/09/15 16:55:06.0591 info    daemon/server-dns :  : dexec.pid=\"21436\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.0727 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"21436\"\n2021/09/15 16:55:06.0754 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"21437\"\n2021/09/15 16:55:06.0756 info    daemon/server-dns :  : dexec.pid=\"21437\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.0852 info    daemon/server-dns :  : dexec.pid=\"21437\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021/09/15 16:55:06.0854 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"21437\"\n2021/09/15 16:55:06.0877 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"21438\"\n2021/09/15 16:55:06.0878 info    daemon/server-dns :  : dexec.pid=\"21438\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.0911 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"21438\"\n2021/09/15 16:55:06.0912 info    daemon/server-grpc/conn=2 : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021/09/15 16:55:06.0913 info    daemon/server-grpc/conn=2 : Generated new /etc/resolver/telepresence.kube-system.local\n2021/09/15 16:55:06.0915 info    daemon/server-grpc/conn=2 : Generated new /etc/resolver/telepresence.tel2-search.local\n2021/09/15 16:55:06.0917 info    daemon/server-grpc/conn=2 : Generated new /etc/resolver/telepresence.ambassador.local\n2021/09/15 16:55:06.0918 info    daemon/server-grpc/conn=2 : Generated new /etc/resolver/telepresence.default.local\n2021/09/15 16:55:06.0919 info    daemon/server-grpc/conn=2 : Generated new /etc/resolver/telepresence.kube-node-lease.local\n2021/09/15 16:55:06.0920 info    daemon/server-grpc/conn=2 : Generated new /etc/resolver/telepresence.kube-public.local\n2021/09/15 16:55:06.0941 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"21439\"\n2021/09/15 16:55:06.0942 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"21439\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.1017 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"21439\"\n2021/09/15 16:55:06.1039 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"21440\"\n2021/09/15 16:55:06.1040 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"21440\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.1133 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"21440\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021/09/15 16:55:06.1135 info    daemon/server-grpc/conn=2 : finished with error: exit status 1 : dexec.pid=\"21440\"\n2021/09/15 16:55:06.1160 info    daemon/server-grpc/conn=2 : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"21441\"\n2021/09/15 16:55:06.1162 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"21441\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.1194 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"21441\"\n2021/09/15 16:55:06.1196 info    daemon/server-grpc/conn=2 : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021/09/15 16:55:06.1220 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"21442\"\n2021/09/15 16:55:06.1222 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"21442\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.1301 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"21442\"\n2021/09/15 16:55:06.1320 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"21443\"\n2021/09/15 16:55:06.1322 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"21443\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.1418 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"21443\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021/09/15 16:55:06.1420 info    daemon/server-grpc/conn=2 : finished with error: exit status 1 : dexec.pid=\"21443\"\n2021/09/15 16:55:06.1440 info    daemon/server-grpc/conn=2 : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"21444\"\n2021/09/15 16:55:06.1441 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"21444\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/15 16:55:06.1468 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"21444\"\n2021/09/15 16:55:07.6433 debug   daemon/server-dns/Server : QTYPE[1] dkaxhzczcdgyrh.cluster.local. -> NOT FOUND\n2021/09/15 16:55:07.6437 debug   daemon/server-dns/Server : QTYPE[28] dkaxhzczcdgyrh.cluster.local. -> NOT FOUND\n2021/09/15 16:55:07.6439 debug   daemon/server-dns/Server : QTYPE[1] abplynvtqbyisno.cluster.local. -> NOT FOUND\n2021/09/15 16:55:07.6440 debug   daemon/server-dns/Server : QTYPE[28] abplynvtqbyisno.cluster.local. -> NOT FOUND\n2021/09/15 16:55:07.6451 debug   daemon/server-dns/Server : QTYPE[28] mhmgkrnjch.cluster.local. -> NOT FOUND\n2021/09/15 16:55:07.6451 debug   daemon/server-dns/Server : QTYPE[1] mhmgkrnjch.cluster.local. -> NOT FOUND\n2021/09/15 17:16:30.0423 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 17:16:30.0424 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 17:16:30.0424 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 17:26:30.0387 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 17:26:30.0388 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 17:26:30.0391 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 17:36:30.0566 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 17:36:30.0568 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 17:36:30.0569 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:11:03.2070 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:11:03.2072 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:11:03.2072 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:20.5468 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:20.5472 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:20.5473 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:25.4311 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4312 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4314 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:25.4392 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4392 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4393 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:25.4396 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4397 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4398 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:25.4561 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4563 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4563 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:25.4629 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4630 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:25.4631 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:31.0581 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:31.1962 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:31.1975 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:31.4009 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:31.4028 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:31.4506 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:31.4518 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:31.4525 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:31.4537 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:31.4563 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:31.4585 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:31.4615 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:31.4653 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:31.5002 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:31.5009 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:34.7635 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:34.8072 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:35.2077 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:35.6056 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:35.6062 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:35.6111 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:35.6136 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:35.6153 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:35.6516 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:35.6546 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:35.7552 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:36.1029 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:36.1172 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:36.5508 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:36.5528 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:36.5636 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:36.5642 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:36.5646 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:36.6021 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:36.6040 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:36.6045 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:39.2544 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:39.2556 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:39.8001 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:40.2010 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:40.2037 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:40.2267 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:40.3137 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:40.3143 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:40.4043 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:42.9169 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:42.9505 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:42.9531 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:41:44.9068 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:41:44.9131 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:41:44.9148 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:58:43.1337 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:58:43.1338 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:58:43.1338 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:58:44.5317 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:58:44.5318 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:58:44.5319 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 18:58:54.9011 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 18:58:54.9019 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 18:58:54.9024 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 19:59:32.0462 debug   daemon/server-dns/Server : QTYPE[1] zhrtvzemyq.cluster.local. -> NOT FOUND\n2021/09/15 19:59:32.0462 debug   daemon/server-dns/Server : QTYPE[28] zhrtvzemyq.cluster.local. -> NOT FOUND\n2021/09/15 19:59:32.0464 debug   daemon/server-dns/Server : QTYPE[1] ybgebfqtgivhxf.cluster.local. -> NOT FOUND\n2021/09/15 19:59:32.0466 debug   daemon/server-dns/Server : QTYPE[28] ybgebfqtgivhxf.cluster.local. -> NOT FOUND\n2021/09/15 19:59:32.0469 debug   daemon/server-dns/Server : QTYPE[1] ozjolubccuuyuow.cluster.local. -> NOT FOUND\n2021/09/15 19:59:32.0486 debug   daemon/server-dns/Server : QTYPE[28] ozjolubccuuyuow.cluster.local. -> NOT FOUND\n2021/09/15 19:59:33.3811 debug   daemon/server-dns/Server : QTYPE[1] llvmprrmvyzgz.cluster.local. -> NOT FOUND\n2021/09/15 19:59:33.3819 debug   daemon/server-dns/Server : QTYPE[28] llvmprrmvyzgz.cluster.local. -> NOT FOUND\n2021/09/15 19:59:33.3829 debug   daemon/server-dns/Server : QTYPE[28] mtbdazgteqsqm.cluster.local. -> NOT FOUND\n2021/09/15 19:59:33.3830 debug   daemon/server-dns/Server : QTYPE[1] mtbdazgteqsqm.cluster.local. -> NOT FOUND\n2021/09/15 19:59:33.3832 debug   daemon/server-dns/Server : QTYPE[1] pcqfqmnpycan.cluster.local. -> NOT FOUND\n2021/09/15 19:59:33.3834 debug   daemon/server-dns/Server : QTYPE[28] pcqfqmnpycan.cluster.local. -> NOT FOUND\n2021/09/15 19:59:34.7250 debug   daemon/server-dns/Server : QTYPE[1] glqehuyh.cluster.local. -> NOT FOUND\n2021/09/15 19:59:34.7254 debug   daemon/server-dns/Server : QTYPE[28] glqehuyh.cluster.local. -> NOT FOUND\n2021/09/15 19:59:34.7264 debug   daemon/server-dns/Server : QTYPE[1] orrceyifpn.cluster.local. -> NOT FOUND\n2021/09/15 19:59:34.7266 debug   daemon/server-dns/Server : QTYPE[28] orrceyifpn.cluster.local. -> NOT FOUND\n2021/09/15 19:59:34.7271 debug   daemon/server-dns/Server : QTYPE[1] vzovgzvo.cluster.local. -> NOT FOUND\n2021/09/15 19:59:34.7275 debug   daemon/server-dns/Server : QTYPE[28] vzovgzvo.cluster.local. -> NOT FOUND\n2021/09/15 19:59:38.7444 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 19:59:38.7446 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 19:59:38.7448 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 19:59:40.1866 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 19:59:40.1868 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 19:59:40.1872 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 19:59:41.7183 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 19:59:41.7234 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 19:59:41.7318 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 19:59:42.8927 debug   daemon/server-dns/Server : QTYPE[1] fvltzbxxkxadr.cluster.local. -> NOT FOUND\n2021/09/15 19:59:42.8944 debug   daemon/server-dns/Server : QTYPE[28] fvltzbxxkxadr.cluster.local. -> NOT FOUND\n2021/09/15 19:59:42.8947 debug   daemon/server-dns/Server : QTYPE[1] uysebeufgxgohx.cluster.local. -> NOT FOUND\n2021/09/15 19:59:42.8963 debug   daemon/server-dns/Server : QTYPE[28] uysebeufgxgohx.cluster.local. -> NOT FOUND\n2021/09/15 19:59:42.8980 debug   daemon/server-dns/Server : QTYPE[1] ruhdngwdqkoydt.cluster.local. -> NOT FOUND\n2021/09/15 19:59:42.8990 debug   daemon/server-dns/Server : QTYPE[28] ruhdngwdqkoydt.cluster.local. -> NOT FOUND\n2021/09/15 20:00:10.6147 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 20:00:10.6153 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 20:00:10.6162 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 20:00:12.1148 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 20:00:12.1157 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 20:00:12.1160 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 20:00:13.6150 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 20:00:13.6157 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 20:00:13.6170 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 21:00:18.4583 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 21:00:18.4584 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 21:00:18.4585 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 21:00:18.6692 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 21:00:18.6694 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 21:00:18.6695 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 21:00:28.7959 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 21:00:28.7969 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 21:00:28.7974 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 21:00:28.7978 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 21:00:28.7987 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 21:00:28.7991 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 21:01:03.7533 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 21:01:03.7924 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 21:01:03.7930 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/15 21:01:03.7970 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/15 21:01:03.7975 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/15 21:01:03.7979 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 02:05:24.5949 debug   daemon/server-dns/Server : QTYPE[1] ysplhmngm.cluster.local. -> NOT FOUND\n2021/09/16 02:05:24.5952 debug   daemon/server-dns/Server : QTYPE[28] ysplhmngm.cluster.local. -> NOT FOUND\n2021/09/16 02:05:24.5957 debug   daemon/server-dns/Server : QTYPE[1] upbkrpiieh.cluster.local. -> NOT FOUND\n2021/09/16 02:05:24.5962 debug   daemon/server-dns/Server : QTYPE[28] upbkrpiieh.cluster.local. -> NOT FOUND\n2021/09/16 02:05:24.5967 debug   daemon/server-dns/Server : QTYPE[1] qdkbnjshqtwnc.cluster.local. -> NOT FOUND\n2021/09/16 02:05:24.5972 debug   daemon/server-dns/Server : QTYPE[28] qdkbnjshqtwnc.cluster.local. -> NOT FOUND\n2021/09/16 02:05:27.7838 debug   daemon/server-dns/Server : QTYPE[1] kexueunltwokcyn.cluster.local. -> NOT FOUND\n2021/09/16 02:05:27.7839 debug   daemon/server-dns/Server : QTYPE[28] kexueunltwokcyn.cluster.local. -> NOT FOUND\n2021/09/16 02:05:27.7847 debug   daemon/server-dns/Server : QTYPE[1] dzezptomrkwptz.cluster.local. -> NOT FOUND\n2021/09/16 02:05:27.7848 debug   daemon/server-dns/Server : QTYPE[28] dzezptomrkwptz.cluster.local. -> NOT FOUND\n2021/09/16 02:05:27.7853 debug   daemon/server-dns/Server : QTYPE[1] qywnrqtbzigoru.cluster.local. -> NOT FOUND\n2021/09/16 02:05:27.7856 debug   daemon/server-dns/Server : QTYPE[28] qywnrqtbzigoru.cluster.local. -> NOT FOUND\n2021/09/16 02:05:28.8967 debug   daemon/server-dns/Server : QTYPE[1] abrtfrjorszr.cluster.local. -> NOT FOUND\n2021/09/16 02:05:28.8971 debug   daemon/server-dns/Server : QTYPE[28] abrtfrjorszr.cluster.local. -> NOT FOUND\n2021/09/16 02:05:28.8976 debug   daemon/server-dns/Server : QTYPE[1] xkaxpuwcbjbsed.cluster.local. -> NOT FOUND\n2021/09/16 02:05:28.8980 debug   daemon/server-dns/Server : QTYPE[28] xkaxpuwcbjbsed.cluster.local. -> NOT FOUND\n2021/09/16 02:05:28.8984 debug   daemon/server-dns/Server : QTYPE[1] mowgwwxpkxf.cluster.local. -> NOT FOUND\n2021/09/16 02:05:28.8986 debug   daemon/server-dns/Server : QTYPE[28] mowgwwxpkxf.cluster.local. -> NOT FOUND\n2021/09/16 02:05:31.0843 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 02:05:31.0844 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 02:05:31.0850 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 02:05:32.5769 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 02:05:32.5771 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 02:05:32.5772 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 02:05:32.5991 debug   daemon/server-dns/Server : QTYPE[1] bagffywtesuvwbg.cluster.local. -> NOT FOUND\n2021/09/16 02:05:32.6006 debug   daemon/server-dns/Server : QTYPE[1] aluptequj.cluster.local. -> NOT FOUND\n2021/09/16 02:05:32.6007 debug   daemon/server-dns/Server : QTYPE[28] bagffywtesuvwbg.cluster.local. -> NOT FOUND\n2021/09/16 02:05:32.6011 debug   daemon/server-dns/Server : QTYPE[28] aluptequj.cluster.local. -> NOT FOUND\n2021/09/16 02:05:32.6013 debug   daemon/server-dns/Server : QTYPE[1] krtmfrzociyvajh.cluster.local. -> NOT FOUND\n2021/09/16 02:05:32.6015 debug   daemon/server-dns/Server : QTYPE[28] krtmfrzociyvajh.cluster.local. -> NOT FOUND\n2021/09/16 02:05:34.0538 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 02:05:34.0545 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 02:05:34.0562 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 02:06:02.8013 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 02:06:02.8024 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 02:06:02.8030 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 02:06:04.3044 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 02:06:04.3051 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 02:06:04.3060 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 02:06:05.8017 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 02:06:05.8030 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 02:06:05.8043 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 03:06:09.8360 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 03:06:09.8362 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 03:06:09.8364 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 03:06:10.0387 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 03:06:10.0389 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 03:06:10.0390 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 03:06:20.2387 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 03:06:20.2735 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 03:06:20.2739 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 03:06:20.2758 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 03:06:20.2777 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 03:06:20.2792 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 03:06:54.8796 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 03:06:54.9323 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 03:06:54.9350 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 03:06:54.9799 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 03:06:54.9811 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 03:06:54.9824 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:43.1738 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:43.1755 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:43.1758 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:44.6671 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:44.6671 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:44.6672 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:44.6672 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:44.6672 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:44.6673 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:45.6695 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:45.6697 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:45.6698 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:45.6701 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:45.6704 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:45.6705 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:46.6726 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:46.6728 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:46.6728 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:48.0587 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:48.0588 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:48.0589 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:49.0604 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:49.0605 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:49.0605 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:49.0606 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:49.0607 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:49.0607 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:50.5576 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:50.5742 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:50.5751 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:50.5752 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:50.5753 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:50.5754 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:52:52.0359 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 08:52:52.0359 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 08:52:52.0360 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 08:56:50.3120 debug   daemon/server-dns/Server : QTYPE[1] asfdsa.cluster.local. -> NOT FOUND\n2021/09/16 08:56:50.3121 debug   daemon/server-dns/Server : QTYPE[28] asfdsa.cluster.local. -> NOT FOUND\n2021/09/16 09:14:44.2191 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 09:14:44.2191 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 09:14:44.2192 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 09:15:02.8252 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 09:15:02.8253 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 09:15:02.8255 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 09:15:14.8137 debug   daemon/server-router/TUN writer : -> TUN icmp 10.43.0.0 -> 10.43.255.255, type destination unreachable, code 3\n2021/09/16 09:15:14.8138 debug   daemon/server-router/TUN writer : -> TUN icmp 10.42.0.0 -> 10.42.0.255, type destination unreachable, code 3\n2021/09/16 09:15:14.8139 debug   daemon/server-router/TUN writer : -> TUN icmp 1.2.3.4 -> 1.2.3.4, type destination unreachable, code 3\n2021/09/16 10:11:43.8374 info    daemon/server-grpc/conn=2 : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021/09/16 10:11:43.8466 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"30754\"\n2021/09/16 10:11:43.8467 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"30754\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 10:11:43.8539 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"30754\"\n2021/09/16 10:11:43.8562 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"30755\"\n2021/09/16 10:11:43.8563 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"30755\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 10:11:43.8647 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"30755\"\n2021/09/16 10:11:43.8713 info    daemon/server-grpc/conn=2 : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"30756\"\n2021/09/16 10:11:43.8716 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"30756\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 10:11:43.8771 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"30756\"\n2021/09/16 10:11:45.8314 debug   daemon/server-dns/Server : QTYPE[1] edhxwytfvigm.cluster.local. -> NOT FOUND\n2021/09/16 10:11:45.8317 debug   daemon/server-dns/Server : QTYPE[28] edhxwytfvigm.cluster.local. -> NOT FOUND\n2021/09/16 10:11:45.8325 debug   daemon/server-dns/Server : QTYPE[1] qkeicqx.cluster.local. -> NOT FOUND\n2021/09/16 10:11:45.8328 debug   daemon/server-dns/Server : QTYPE[28] qkeicqx.cluster.local. -> NOT FOUND\n2021/09/16 10:11:45.8331 debug   daemon/server-dns/Server : QTYPE[1] ppzsutr.cluster.local. -> NOT FOUND\n2021/09/16 10:11:45.8336 debug   daemon/server-dns/Server : QTYPE[28] ppzsutr.cluster.local. -> NOT FOUND\n2021/09/16 11:48:09.9530 info    daemon/server-grpc/conn=2 : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021/09/16 11:48:09.9698 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"36755\"\n2021/09/16 11:48:09.9701 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36755\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:09.9828 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"36755\"\n2021/09/16 11:48:09.9855 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"36756\"\n2021/09/16 11:48:09.9857 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36756\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:09.9945 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36756\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021/09/16 11:48:09.9947 info    daemon/server-grpc/conn=2 : finished with error: exit status 1 : dexec.pid=\"36756\"\n2021/09/16 11:48:10.0013 info    daemon/server-grpc/conn=2 : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"36757\"\n2021/09/16 11:48:10.0015 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36757\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:10.0062 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"36757\"\n2021/09/16 11:48:11.7403 debug   daemon/server-dns/Server : QTYPE[1] nhxiznicnn.cluster.local. -> NOT FOUND\n2021/09/16 11:48:11.7406 debug   daemon/server-dns/Server : QTYPE[28] nhxiznicnn.cluster.local. -> NOT FOUND\n2021/09/16 11:48:11.7409 debug   daemon/server-dns/Server : QTYPE[1] ajzmlhkgconl.cluster.local. -> NOT FOUND\n2021/09/16 11:48:11.7411 debug   daemon/server-dns/Server : QTYPE[28] ajzmlhkgconl.cluster.local. -> NOT FOUND\n2021/09/16 11:48:11.7417 debug   daemon/server-dns/Server : QTYPE[1] bnasvzj.cluster.local. -> NOT FOUND\n2021/09/16 11:48:11.7420 debug   daemon/server-dns/Server : QTYPE[28] bnasvzj.cluster.local. -> NOT FOUND\n2021/09/16 11:48:13.0571 info    daemon/server-grpc/conn=2 : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021/09/16 11:48:13.0635 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"36760\"\n2021/09/16 11:48:13.0638 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36760\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:13.0811 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"36760\"\n2021/09/16 11:48:13.0845 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"36761\"\n2021/09/16 11:48:13.0847 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36761\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:13.0942 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36761\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021/09/16 11:48:13.0944 info    daemon/server-grpc/conn=2 : finished with error: exit status 1 : dexec.pid=\"36761\"\n2021/09/16 11:48:13.0966 info    daemon/server-grpc/conn=2 : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"36762\"\n2021/09/16 11:48:13.0967 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36762\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:13.0996 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"36762\"\n2021/09/16 11:48:14.5843 debug   daemon/server-dns/Server : QTYPE[1] nbwrpojuplzelrv.cluster.local. -> NOT FOUND\n2021/09/16 11:48:14.5844 debug   daemon/server-dns/Server : QTYPE[28] nbwrpojuplzelrv.cluster.local. -> NOT FOUND\n2021/09/16 11:48:14.5847 debug   daemon/server-dns/Server : QTYPE[1] bjgcoyxqkza.cluster.local. -> NOT FOUND\n2021/09/16 11:48:14.5857 debug   daemon/server-dns/Server : QTYPE[28] bjgcoyxqkza.cluster.local. -> NOT FOUND\n2021/09/16 11:48:14.5864 debug   daemon/server-dns/Server : QTYPE[1] kpvwofabpbmwypp.cluster.local. -> NOT FOUND\n2021/09/16 11:48:14.5866 debug   daemon/server-dns/Server : QTYPE[28] kpvwofabpbmwypp.cluster.local. -> NOT FOUND\n2021/09/16 11:48:31.4011 info    daemon/server-grpc/conn=2 : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021/09/16 11:48:31.4074 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"36771\"\n2021/09/16 11:48:31.4081 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36771\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:31.4225 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"36771\"\n2021/09/16 11:48:31.4253 info    daemon/server-grpc/conn=2 : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"36772\"\n2021/09/16 11:48:31.4256 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36772\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:31.4342 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36772\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021/09/16 11:48:31.4344 info    daemon/server-grpc/conn=2 : finished with error: exit status 1 : dexec.pid=\"36772\"\n2021/09/16 11:48:31.4369 info    daemon/server-grpc/conn=2 : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"36773\"\n2021/09/16 11:48:31.4370 info    daemon/server-grpc/conn=2 :  : dexec.pid=\"36773\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 11:48:31.4405 info    daemon/server-grpc/conn=2 : finished successfully: exit status 0 : dexec.pid=\"36773\"\n2021/09/16 11:48:33.9617 debug   daemon/server-dns/Server : QTYPE[1] gotaavy.cluster.local. -> NOT FOUND\n2021/09/16 11:48:33.9632 debug   daemon/server-dns/Server : QTYPE[28] gotaavy.cluster.local. -> NOT FOUND\n2021/09/16 11:48:33.9631 debug   daemon/server-dns/Server : QTYPE[28] xkpgangnzunnfos.cluster.local. -> NOT FOUND\n2021/09/16 11:48:33.9635 debug   daemon/server-dns/Server : QTYPE[1] xkpgangnzunnfos.cluster.local. -> NOT FOUND\n2021/09/16 11:48:33.9647 debug   daemon/server-dns/Server : QTYPE[28] uqvemntl.cluster.local. -> NOT FOUND\n2021/09/16 11:48:33.9648 debug   daemon/server-dns/Server : QTYPE[1] uqvemntl.cluster.local. -> NOT FOUND\n2021/09/16 13:03:01.1865 debug   daemon/server-grpc/conn=3 : Received gRPC Quit\n2021/09/16 13:03:01.1870 debug   daemon/server-router/TUN reader : goroutine \"/daemon/server-router/TUN reader\" exited without error\n2021/09/16 13:03:01.1870 info    daemon/daemon-quit : Shutting down connector\n2021/09/16 13:03:01.1876 debug   daemon/daemon-quit : Sending quit message to connector\n2021/09/16 13:03:01.1880 debug   daemon/daemon-quit : Connector shutdown complete\n2021/09/16 13:03:01.3928 debug   daemon/daemon-quit : goroutine \"/daemon/daemon-quit\" exited without error\n2021/09/16 13:03:01.3931 info    daemon/server-dns/Server:shutdown_logger : shutting down (gracefully)...\n2021/09/16 13:03:01.3931 debug   daemon/server-router/MGR stream : goroutine \"/daemon/server-router/MGR stream\" exited without error\n2021/09/16 13:03:01.3932 info    daemon/server-router:shutdown_logger : shutting down (gracefully)...\n2021/09/16 13:03:01.3933 info    daemon/server-dns:shutdown_logger : shutting down (gracefully)...\n2021/09/16 13:03:01.3935 error   daemon/watch-cluster-info : goroutine \"/daemon/watch-cluster-info\" exited with error: error when reading WatchClusterInfo: rpc error: code = Unavailable desc = error reading from server: EOF\n2021/09/16 13:03:01.3936 info    daemon:shutdown_logger : shutting down (gracefully)...\n2021/09/16 13:03:01.3934 debug   daemon/server-dns/Server/127.0.0.1:54973 : goroutine \"/daemon/server-dns/Server/127.0.0.1:54973\" exited without error\n2021/09/16 13:03:01.3936 debug   daemon/server-router/TUN writer : goroutine \"/daemon/server-router/TUN writer\" exited without error\n2021/09/16 13:03:01.3940 debug   daemon/server-dns/Server : goroutine \"/daemon/server-dns/Server\" exited without error\n2021/09/16 13:03:01.3941 debug   daemon/server-router : goroutine \"/daemon/server-router\" exited without error\n2021/09/16 13:03:01.3948 debug   daemon/server-grpc : gRPC server ended\n2021/09/16 13:03:01.3950 debug   daemon/server-grpc : goroutine \"/daemon/server-grpc\" exited without error\n2021/09/16 13:03:01.4085 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"41973\"\n2021/09/16 13:03:01.4089 info    daemon/server-dns :  : dexec.pid=\"41973\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 13:03:01.4221 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"41973\"\n2021/09/16 13:03:01.4250 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"41974\"\n2021/09/16 13:03:01.4253 info    daemon/server-dns :  : dexec.pid=\"41974\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 13:03:01.4335 info    daemon/server-dns :  : dexec.pid=\"41974\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021/09/16 13:03:01.4336 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"41974\"\n2021/09/16 13:03:01.4393 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"41975\"\n2021/09/16 13:03:01.4394 info    daemon/server-dns :  : dexec.pid=\"41975\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021/09/16 13:03:01.4431 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"41975\"\n2021/09/16 13:03:01.4432 debug   daemon/server-dns : goroutine \"/daemon/server-dns\" exited without error\n2021/09/16 13:03:01.4432 debug   daemon/background-metriton : goroutine \"/daemon/background-metriton\" exited without error\n2021/09/16 13:03:01.4433 info    daemon:shutdown_status :   final goroutine statuses:\n2021/09/16 13:03:01.4434 info    daemon:shutdown_status :     /daemon/background-metriton: exited without error\n2021/09/16 13:03:01.4434 info    daemon:shutdown_status :     /daemon/daemon-quit        : exited without error\n2021/09/16 13:03:01.4434 info    daemon:shutdown_status :     /daemon/server-dns         : exited without error\n2021/09/16 13:03:01.4434 info    daemon:shutdown_status :     /daemon/server-grpc        : exited without error\n2021/09/16 13:03:01.4434 info    daemon:shutdown_status :     /daemon/server-router      : exited without error\n2021/09/16 13:03:01.4435 info    daemon:shutdown_status :     /daemon/watch-cluster-info : exited with error\n2021/09/16 13:03:01.4435 error    : error when reading WatchClusterInfo: rpc error: code = Unavailable desc = error reading from server: EOF\ntelepresence: error: error when reading WatchClusterInfo: rpc error: code = Unavailable desc = error reading from server: EOF\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/daemon-20210916T130402.log",
    "content": "2021-09-16 13:03:18.7998 debug   Logging at this level \"debug\"\n2021-09-16 13:03:18.8006 info    ---\n2021-09-16 13:03:18.8006 info    Telepresence daemon v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:03:18.8006 info    PID is 42026\n2021-09-16 13:03:18.8006 info    \n2021-09-16 13:03:18.8007 debug   Listener opened\n2021-09-16 13:03:18.8379 info    daemon/server-grpc : gRPC server started\n2021-09-16 13:03:18.8379 debug   daemon/server-router/TUN reader : Waiting until manager gRPC is configured\n2021-09-16 13:03:18.8379 debug   daemon/server-router/MGR stream : Waiting until manager gRPC is configured\n2021-09-16 13:03:36.8925 info    daemon/server-grpc/conn=2 : Adding also-proxy subnet 1.2.3.4/32\n2021-09-16 13:03:36.9395 info    daemon/watch-cluster-info : Adding service subnet 10.43.0.0/16\n2021-09-16 13:03:36.9397 info    daemon/watch-cluster-info : Adding pod subnet 10.42.0.0/24\n2021-09-16 13:03:36.9407 info    daemon/watch-cluster-info : Setting cluster DNS to 10.43.0.10\n2021-09-16 13:03:36.9408 info    daemon/watch-cluster-info : Setting cluster domain to \"cluster.local.\"\n2021-09-16 13:03:36.9410 debug   daemon/server-router/TUN reader : TUN read loop starting\n2021-09-16 13:03:36.9413 debug   daemon/server-router/MGR stream : MGR read loop starting\n2021-09-16 13:03:36.9423 info    daemon/server-dns : Generated new /etc/resolver/telepresence.local\n2021-09-16 13:03:36.9431 debug   daemon/server-dns/SearchPaths : [] -> [ambassador default kube-node-lease kube-public kube-system]\n2021-09-16 13:03:36.9455 info    daemon/server-dns/SearchPaths : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021-09-16 13:03:36.9462 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.default.local\n2021-09-16 13:03:36.9477 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-node-lease.local\n2021-09-16 13:03:36.9484 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-public.local\n2021-09-16 13:03:36.9489 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-system.local\n2021-09-16 13:03:36.9495 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.tel2-search.local\n2021-09-16 13:03:36.9499 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.ambassador.local\n2021-09-16 13:03:36.9516 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"42097\"\n2021-09-16 13:03:36.9519 info    daemon/server-dns :  : dexec.pid=\"42097\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:03:36.9643 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42097\"\n2021-09-16 13:03:36.9671 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"42098\"\n2021-09-16 13:03:36.9673 info    daemon/server-dns :  : dexec.pid=\"42098\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:03:36.9786 info    daemon/server-dns :  : dexec.pid=\"42098\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021-09-16 13:03:36.9791 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"42098\"\n2021-09-16 13:03:36.9815 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"42099\"\n2021-09-16 13:03:36.9817 info    daemon/server-dns :  : dexec.pid=\"42099\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:03:36.9844 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42099\"\n2021-09-16 13:03:36.9880 debug   daemon/server-router/MGR stream : setting tunnel's peer version to 1\n2021-09-16 13:03:38.4784 debug   daemon/server-dns/Server : QTYPE[1] ykbcmbcwnwgwvjt.cluster.local. -> NOT FOUND\n2021-09-16 13:03:38.4786 debug   daemon/server-dns/Server : QTYPE[28] ykbcmbcwnwgwvjt.cluster.local. -> NOT FOUND\n2021-09-16 13:03:38.4791 debug   daemon/server-dns/Server : QTYPE[1] znqoxvhdzp.cluster.local. -> NOT FOUND\n2021-09-16 13:03:38.4795 debug   daemon/server-dns/Server : QTYPE[28] znqoxvhdzp.cluster.local. -> NOT FOUND\n2021-09-16 13:03:38.4802 debug   daemon/server-dns/Server : QTYPE[1] lvwmxfvxyahtpa.cluster.local. -> NOT FOUND\n2021-09-16 13:03:38.4806 debug   daemon/server-dns/Server : QTYPE[28] lvwmxfvxyahtpa.cluster.local. -> NOT FOUND\n2021-09-16 13:03:43.0249 debug   daemon/server-grpc/conn=3 : Received gRPC Quit\n2021-09-16 13:03:43.0256 debug   daemon/server-router/TUN reader : goroutine \"/daemon/server-router/TUN reader\" exited without error\n2021-09-16 13:03:43.0257 info    daemon/daemon-quit : Shutting down connector\n2021-09-16 13:03:43.0269 debug   daemon/daemon-quit : Sending quit message to connector\n2021-09-16 13:03:43.0279 debug   daemon/daemon-quit : Connector shutdown complete\n2021-09-16 13:03:43.2331 debug   daemon/daemon-quit : goroutine \"/daemon/daemon-quit\" exited without error\n2021-09-16 13:03:43.2334 info    daemon/server-dns/Server:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:43.2335 debug   daemon/server-router/MGR stream : goroutine \"/daemon/server-router/MGR stream\" exited without error\n2021-09-16 13:03:43.2337 debug   daemon/server-dns/SearchPaths : goroutine \"/daemon/server-dns/SearchPaths\" exited without error\n2021-09-16 13:03:43.2334 info    daemon:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:43.2337 info    daemon/server-dns:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:43.2336 debug   daemon/server-router/TUN writer : goroutine \"/daemon/server-router/TUN writer\" exited without error\n2021-09-16 13:03:43.2339 debug   daemon/server-dns/Server/127.0.0.1:52776 : goroutine \"/daemon/server-dns/Server/127.0.0.1:52776\" exited without error\n2021-09-16 13:03:43.2337 info    daemon/server-router:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:03:43.2343 debug   daemon/server-dns/Server : goroutine \"/daemon/server-dns/Server\" exited without error\n2021-09-16 13:03:43.2343 debug   daemon/server-router : goroutine \"/daemon/server-router\" exited without error\n2021-09-16 13:03:43.2341 debug   daemon/watch-cluster-info : goroutine \"/daemon/watch-cluster-info\" exited without error\n2021-09-16 13:03:43.2359 debug   daemon/server-grpc : gRPC server ended\n2021-09-16 13:03:43.2361 debug   daemon/server-grpc : goroutine \"/daemon/server-grpc\" exited without error\n2021-09-16 13:03:43.2425 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"42103\"\n2021-09-16 13:03:43.2429 info    daemon/server-dns :  : dexec.pid=\"42103\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:03:43.2600 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42103\"\n2021-09-16 13:03:43.2630 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"42104\"\n2021-09-16 13:03:43.2632 info    daemon/server-dns :  : dexec.pid=\"42104\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:03:43.2722 info    daemon/server-dns :  : dexec.pid=\"42104\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021-09-16 13:03:43.2724 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"42104\"\n2021-09-16 13:03:43.2745 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"42105\"\n2021-09-16 13:03:43.2746 info    daemon/server-dns :  : dexec.pid=\"42105\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:03:43.2779 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42105\"\n2021-09-16 13:03:43.2780 debug   daemon/server-dns : goroutine \"/daemon/server-dns\" exited without error\n2021-09-16 13:03:43.2781 debug   daemon/background-metriton : goroutine \"/daemon/background-metriton\" exited without error\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/daemon-20210916T130624.log",
    "content": "2021-09-16 13:04:02.4495 debug   Logging at this level \"debug\"\n2021-09-16 13:04:02.4504 info    ---\n2021-09-16 13:04:02.4504 info    Telepresence daemon v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:04:02.4504 info    PID is 42224\n2021-09-16 13:04:02.4504 info    \n2021-09-16 13:04:02.4506 debug   Listener opened\n2021-09-16 13:04:02.4883 debug   daemon/server-router/TUN reader : Waiting until manager gRPC is configured\n2021-09-16 13:04:02.4883 info    daemon/server-grpc : gRPC server started\n2021-09-16 13:04:02.4883 debug   daemon/server-router/MGR stream : Waiting until manager gRPC is configured\n2021-09-16 13:04:12.9763 info    daemon/server-grpc/conn=2 : Adding also-proxy subnet 1.2.3.4/32\n2021-09-16 13:04:13.0247 info    daemon/watch-cluster-info : Adding service subnet 10.43.0.0/16\n2021-09-16 13:04:13.0249 info    daemon/watch-cluster-info : Adding pod subnet 10.42.0.0/24\n2021-09-16 13:04:13.0257 info    daemon/watch-cluster-info : Setting cluster DNS to 10.43.0.10\n2021-09-16 13:04:13.0257 info    daemon/watch-cluster-info : Setting cluster domain to \"cluster.local.\"\n2021-09-16 13:04:13.0259 debug   daemon/server-router/TUN reader : TUN read loop starting\n2021-09-16 13:04:13.0263 debug   daemon/server-router/MGR stream : MGR read loop starting\n2021-09-16 13:04:13.0271 info    daemon/server-dns : Generated new /etc/resolver/telepresence.local\n2021-09-16 13:04:13.0276 debug   daemon/server-dns/SearchPaths : [] -> [ambassador default kube-node-lease kube-public kube-system]\n2021-09-16 13:04:13.0278 info    daemon/server-dns/SearchPaths : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021-09-16 13:04:13.0303 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.tel2-search.local\n2021-09-16 13:04:13.0314 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.ambassador.local\n2021-09-16 13:04:13.0319 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.default.local\n2021-09-16 13:04:13.0325 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-node-lease.local\n2021-09-16 13:04:13.0330 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-public.local\n2021-09-16 13:04:13.0335 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-system.local\n2021-09-16 13:04:13.0340 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"42228\"\n2021-09-16 13:04:13.0343 info    daemon/server-dns :  : dexec.pid=\"42228\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:04:13.0485 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42228\"\n2021-09-16 13:04:13.0512 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"42229\"\n2021-09-16 13:04:13.0514 info    daemon/server-dns :  : dexec.pid=\"42229\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:04:13.0603 info    daemon/server-dns :  : dexec.pid=\"42229\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021-09-16 13:04:13.0606 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"42229\"\n2021-09-16 13:04:13.0627 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"42230\"\n2021-09-16 13:04:13.0629 info    daemon/server-dns :  : dexec.pid=\"42230\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:04:13.0655 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42230\"\n2021-09-16 13:04:13.1154 debug   daemon/server-router/MGR stream : setting tunnel's peer version to 1\n2021-09-16 13:04:14.7134 debug   daemon/server-dns/Server : QTYPE[1] ilyjgdjnzvuq.cluster.local. -> NOT FOUND\n2021-09-16 13:04:14.7139 debug   daemon/server-dns/Server : QTYPE[28] ilyjgdjnzvuq.cluster.local. -> NOT FOUND\n2021-09-16 13:04:14.7144 debug   daemon/server-dns/Server : QTYPE[1] ziwmaszezjymox.cluster.local. -> NOT FOUND\n2021-09-16 13:04:14.7144 debug   daemon/server-dns/Server : QTYPE[28] ziwmaszezjymox.cluster.local. -> NOT FOUND\n2021-09-16 13:04:14.7149 debug   daemon/server-dns/Server : QTYPE[1] hdyagrken.cluster.local. -> NOT FOUND\n2021-09-16 13:04:14.7155 debug   daemon/server-dns/Server : QTYPE[28] hdyagrken.cluster.local. -> NOT FOUND\n2021-09-16 13:05:28.7657 debug   daemon/server-grpc/conn=3 : Received gRPC Quit\n2021-09-16 13:05:28.7661 debug   daemon/server-router/TUN reader : goroutine \"/daemon/server-router/TUN reader\" exited without error\n2021-09-16 13:05:28.7662 info    daemon/daemon-quit : Shutting down connector\n2021-09-16 13:05:28.7666 debug   daemon/daemon-quit : Sending quit message to connector\n2021-09-16 13:05:28.7672 debug   daemon/daemon-quit : Connector shutdown complete\n2021-09-16 13:05:28.9692 debug   daemon/daemon-quit : goroutine \"/daemon/daemon-quit\" exited without error\n2021-09-16 13:05:28.9695 info    daemon/server-dns/Server:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:05:28.9697 debug   daemon/server-router/MGR stream : goroutine \"/daemon/server-router/MGR stream\" exited without error\n2021-09-16 13:05:28.9698 debug   daemon/server-router/TUN writer : goroutine \"/daemon/server-router/TUN writer\" exited without error\n2021-09-16 13:05:28.9699 debug   daemon/server-dns/Server/127.0.0.1:60044 : goroutine \"/daemon/server-dns/Server/127.0.0.1:60044\" exited without error\n2021-09-16 13:05:28.9697 info    daemon/server-dns:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:05:28.9698 debug   daemon/watch-cluster-info : goroutine \"/daemon/watch-cluster-info\" exited without error\n2021-09-16 13:05:28.9697 debug   daemon/server-dns/SearchPaths : goroutine \"/daemon/server-dns/SearchPaths\" exited without error\n2021-09-16 13:05:28.9696 info    daemon:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:05:28.9699 info    daemon/server-router:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:05:28.9702 debug   daemon/server-dns/Server : goroutine \"/daemon/server-dns/Server\" exited without error\n2021-09-16 13:05:28.9710 debug   daemon/server-router : goroutine \"/daemon/server-router\" exited without error\n2021-09-16 13:05:28.9712 debug   daemon/server-grpc : gRPC server ended\n2021-09-16 13:05:28.9713 debug   daemon/server-grpc : goroutine \"/daemon/server-grpc\" exited without error\n2021-09-16 13:05:28.9790 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"42537\"\n2021-09-16 13:05:28.9793 info    daemon/server-dns :  : dexec.pid=\"42537\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:05:28.9931 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42537\"\n2021-09-16 13:05:28.9956 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"42538\"\n2021-09-16 13:05:28.9958 info    daemon/server-dns :  : dexec.pid=\"42538\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:05:29.0076 info    daemon/server-dns :  : dexec.pid=\"42538\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021-09-16 13:05:29.0079 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"42538\"\n2021-09-16 13:05:29.0101 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"42539\"\n2021-09-16 13:05:29.0103 info    daemon/server-dns :  : dexec.pid=\"42539\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:05:29.0140 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42539\"\n2021-09-16 13:05:29.0140 debug   daemon/server-dns : goroutine \"/daemon/server-dns\" exited without error\n2021-09-16 13:05:29.0141 debug   daemon/background-metriton : goroutine \"/daemon/background-metriton\" exited without error\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/daemon-20210916T130643.log",
    "content": "2021-09-16 13:06:24.1581 debug   Logging at this level \"debug\"\n2021-09-16 13:06:24.1587 info    ---\n2021-09-16 13:06:24.1587 info    Telepresence daemon v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:06:24.1587 info    PID is 42651\n2021-09-16 13:06:24.1587 info    \n2021-09-16 13:06:24.1590 debug   Listener opened\n2021-09-16 13:06:24.1952 info    daemon/server-grpc : gRPC server started\n2021-09-16 13:06:24.1952 debug   daemon/server-router/MGR stream : Waiting until manager gRPC is configured\n2021-09-16 13:06:24.1952 debug   daemon/server-router/TUN reader : Waiting until manager gRPC is configured\n2021-09-16 13:06:25.6951 info    daemon/server-grpc/conn=2 : Adding also-proxy subnet 1.2.3.4/32\n2021-09-16 13:06:25.7534 info    daemon/watch-cluster-info : Adding service subnet 10.43.0.0/16\n2021-09-16 13:06:25.7535 info    daemon/watch-cluster-info : Adding pod subnet 10.42.0.0/24\n2021-09-16 13:06:25.7544 info    daemon/watch-cluster-info : Setting cluster DNS to 10.43.0.10\n2021-09-16 13:06:25.7545 info    daemon/watch-cluster-info : Setting cluster domain to \"cluster.local.\"\n2021-09-16 13:06:25.7549 debug   daemon/server-router/TUN reader : TUN read loop starting\n2021-09-16 13:06:25.7552 debug   daemon/server-router/MGR stream : MGR read loop starting\n2021-09-16 13:06:25.7561 info    daemon/server-dns : Generated new /etc/resolver/telepresence.local\n2021-09-16 13:06:25.7568 debug   daemon/server-dns/SearchPaths : [] -> [ambassador default kube-node-lease kube-public kube-system]\n2021-09-16 13:06:25.7595 info    daemon/server-dns/SearchPaths : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021-09-16 13:06:25.7605 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.default.local\n2021-09-16 13:06:25.7614 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-node-lease.local\n2021-09-16 13:06:25.7625 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-public.local\n2021-09-16 13:06:25.7629 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"42659\"\n2021-09-16 13:06:25.7630 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-system.local\n2021-09-16 13:06:25.7631 info    daemon/server-dns :  : dexec.pid=\"42659\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:25.7635 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.tel2-search.local\n2021-09-16 13:06:25.7639 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.ambassador.local\n2021-09-16 13:06:25.7785 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42659\"\n2021-09-16 13:06:25.7810 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"42660\"\n2021-09-16 13:06:25.7812 info    daemon/server-dns :  : dexec.pid=\"42660\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:25.7902 info    daemon/server-dns :  : dexec.pid=\"42660\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021-09-16 13:06:25.7905 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"42660\"\n2021-09-16 13:06:25.7929 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"42661\"\n2021-09-16 13:06:25.7931 info    daemon/server-dns :  : dexec.pid=\"42661\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:25.7968 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42661\"\n2021-09-16 13:06:25.8220 debug   daemon/server-router/MGR stream : setting tunnel's peer version to 1\n2021-09-16 13:06:28.0021 debug   daemon/server-dns/Server : QTYPE[1] larefwkqdqh.cluster.local. -> NOT FOUND\n2021-09-16 13:06:28.0022 debug   daemon/server-dns/Server : QTYPE[28] larefwkqdqh.cluster.local. -> NOT FOUND\n2021-09-16 13:06:28.0031 debug   daemon/server-dns/Server : QTYPE[1] rybugcmzgv.cluster.local. -> NOT FOUND\n2021-09-16 13:06:28.0031 debug   daemon/server-dns/Server : QTYPE[28] rybugcmzgv.cluster.local. -> NOT FOUND\n2021-09-16 13:06:28.0043 debug   daemon/server-dns/Server : QTYPE[1] palcebzx.cluster.local. -> NOT FOUND\n2021-09-16 13:06:28.0043 debug   daemon/server-dns/Server : QTYPE[28] palcebzx.cluster.local. -> NOT FOUND\n2021-09-16 13:06:29.1253 debug   daemon/server-grpc/conn=3 : Received gRPC Quit\n2021-09-16 13:06:29.1261 debug   daemon/server-router/TUN reader : goroutine \"/daemon/server-router/TUN reader\" exited without error\n2021-09-16 13:06:29.1264 info    daemon/daemon-quit : Shutting down connector\n2021-09-16 13:06:29.1276 debug   daemon/daemon-quit : Sending quit message to connector\n2021-09-16 13:06:29.1299 debug   daemon/daemon-quit : Connector shutdown complete\n2021-09-16 13:06:29.3343 debug   daemon/daemon-quit : goroutine \"/daemon/daemon-quit\" exited without error\n2021-09-16 13:06:29.3346 info    daemon/server-dns/Server:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:06:29.3347 debug   daemon/server-router/MGR stream : goroutine \"/daemon/server-router/MGR stream\" exited without error\n2021-09-16 13:06:29.3347 info    daemon:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:06:29.3348 info    daemon/server-dns:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:06:29.3348 debug   daemon/server-dns/SearchPaths : goroutine \"/daemon/server-dns/SearchPaths\" exited without error\n2021-09-16 13:06:29.3350 info    daemon/server-router:shutdown_logger : shutting down (gracefully)...\n2021-09-16 13:06:29.3350 debug   daemon/server-router/TUN writer : goroutine \"/daemon/server-router/TUN writer\" exited without error\n2021-09-16 13:06:29.3352 debug   daemon/server-router : goroutine \"/daemon/server-router\" exited without error\n2021-09-16 13:06:29.3350 debug   daemon/server-dns/Server/127.0.0.1:60423 : goroutine \"/daemon/server-dns/Server/127.0.0.1:60423\" exited without error\n2021-09-16 13:06:29.3351 debug   daemon/watch-cluster-info : goroutine \"/daemon/watch-cluster-info\" exited without error\n2021-09-16 13:06:29.3355 debug   daemon/server-dns/Server : goroutine \"/daemon/server-dns/Server\" exited without error\n2021-09-16 13:06:29.3367 debug   daemon/server-grpc : gRPC server ended\n2021-09-16 13:06:29.3367 debug   daemon/server-grpc : goroutine \"/daemon/server-grpc\" exited without error\n2021-09-16 13:06:29.3433 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"42664\"\n2021-09-16 13:06:29.3435 info    daemon/server-dns :  : dexec.pid=\"42664\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:29.3587 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42664\"\n2021-09-16 13:06:29.3618 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"42665\"\n2021-09-16 13:06:29.3621 info    daemon/server-dns :  : dexec.pid=\"42665\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:29.3717 info    daemon/server-dns :  : dexec.pid=\"42665\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021-09-16 13:06:29.3876 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"42665\"\n2021-09-16 13:06:29.3896 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"42666\"\n2021-09-16 13:06:29.3906 info    daemon/server-dns :  : dexec.pid=\"42666\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:29.3931 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42666\"\n2021-09-16 13:06:29.3931 debug   daemon/server-dns : goroutine \"/daemon/server-dns\" exited without error\n2021-09-16 13:06:29.3932 debug   daemon/background-metriton : goroutine \"/daemon/background-metriton\" exited without error\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/daemon.log",
    "content": "2021-09-16 13:06:43.2401 debug   Logging at this level \"debug\"\n2021-09-16 13:06:43.2405 info    ---\n2021-09-16 13:06:43.2405 info    Telepresence daemon v2.4.3-98-g0585bbd1-1631811285 (api v3) starting...\n2021-09-16 13:06:43.2406 info    PID is 42745\n2021-09-16 13:06:43.2406 info    \n2021-09-16 13:06:43.2407 debug   Listener opened\n2021-09-16 13:06:43.2737 info    daemon/server-grpc : gRPC server started\n2021-09-16 13:06:43.2738 debug   daemon/server-router/MGR stream : Waiting until manager gRPC is configured\n2021-09-16 13:06:43.2738 debug   daemon/server-router/TUN reader : Waiting until manager gRPC is configured\n2021-09-16 13:06:55.0000 info    daemon/server-grpc/conn=2 : Adding also-proxy subnet 1.2.3.4/32\n2021-09-16 13:06:55.0459 info    daemon/watch-cluster-info : Adding service subnet 10.43.0.0/16\n2021-09-16 13:06:55.0460 info    daemon/watch-cluster-info : Adding pod subnet 10.42.0.0/24\n2021-09-16 13:06:55.0467 info    daemon/watch-cluster-info : Setting cluster DNS to 10.43.0.10\n2021-09-16 13:06:55.0468 info    daemon/watch-cluster-info : Setting cluster domain to \"cluster.local.\"\n2021-09-16 13:06:55.0469 debug   daemon/server-router/TUN reader : TUN read loop starting\n2021-09-16 13:06:55.0473 debug   daemon/server-router/MGR stream : MGR read loop starting\n2021-09-16 13:06:55.0477 info    daemon/server-dns : Generated new /etc/resolver/telepresence.local\n2021-09-16 13:06:55.0482 debug   daemon/server-dns/SearchPaths : [] -> [ambassador default kube-node-lease kube-public kube-system]\n2021-09-16 13:06:55.0483 info    daemon/server-dns/SearchPaths : setting search paths ambassador default kube-node-lease kube-public kube-system\n2021-09-16 13:06:55.0505 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-system.local\n2021-09-16 13:06:55.0514 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.tel2-search.local\n2021-09-16 13:06:55.0520 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.ambassador.local\n2021-09-16 13:06:55.0528 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.default.local\n2021-09-16 13:06:55.0534 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-node-lease.local\n2021-09-16 13:06:55.0539 info    daemon/server-dns/SearchPaths : Generated new /etc/resolver/telepresence.kube-public.local\n2021-09-16 13:06:55.0554 info    daemon/server-dns : started command [\"killall\" \"-HUP\" \"mDNSResponder\"] : dexec.pid=\"42754\"\n2021-09-16 13:06:55.0557 info    daemon/server-dns :  : dexec.pid=\"42754\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:55.0697 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42754\"\n2021-09-16 13:06:55.0724 info    daemon/server-dns : started command [\"killall\" \"mDNSResponderHelper\"] : dexec.pid=\"42755\"\n2021-09-16 13:06:55.0726 info    daemon/server-dns :  : dexec.pid=\"42755\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:55.0814 info    daemon/server-dns :  : dexec.pid=\"42755\" dexec.stream=\"stdout+stderr\" dexec.data=\"No matching processes were found\\n\"\n2021-09-16 13:06:55.0816 info    daemon/server-dns : finished with error: exit status 1 : dexec.pid=\"42755\"\n2021-09-16 13:06:55.0836 info    daemon/server-dns : started command [\"dscacheutil\" \"-flushcache\"] : dexec.pid=\"42756\"\n2021-09-16 13:06:55.0838 info    daemon/server-dns :  : dexec.pid=\"42756\" dexec.stream=\"stdin\" dexec.err=\"EOF\"\n2021-09-16 13:06:55.0870 info    daemon/server-dns : finished successfully: exit status 0 : dexec.pid=\"42756\"\n2021-09-16 13:06:55.1605 debug   daemon/server-router/MGR stream : setting tunnel's peer version to 1\n2021-09-16 13:06:56.8504 debug   daemon/server-dns/Server : QTYPE[1] kojjgoxv.cluster.local. -> NOT FOUND\n2021-09-16 13:06:56.8506 debug   daemon/server-dns/Server : QTYPE[28] kojjgoxv.cluster.local. -> NOT FOUND\n2021-09-16 13:06:56.8507 debug   daemon/server-dns/Server : QTYPE[1] vuujrnwogkmwdld.cluster.local. -> NOT FOUND\n2021-09-16 13:06:56.8513 debug   daemon/server-dns/Server : QTYPE[28] vuujrnwogkmwdld.cluster.local. -> NOT FOUND\n2021-09-16 13:06:56.8515 debug   daemon/server-dns/Server : QTYPE[1] gzmxavhqnmpqzl.cluster.local. -> NOT FOUND\n2021-09-16 13:06:56.8517 debug   daemon/server-dns/Server : QTYPE[28] gzmxavhqnmpqzl.cluster.local. -> NOT FOUND\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/echo-auto-inject-6496f77cbd-n86nc",
    "content": "time=\"2021-09-16 17:07:04.8111\" level=info msg=\"Logging at this level \\\"info\\\"\"\ntime=\"2021-09-16 17:07:04.8112\" level=info msg=\"Traffic Agent v2.4.2 [pid:1]\"\ntime=\"2021-09-16 17:07:04.8112\" level=info msg=\"{Name:echo-auto-inject Namespace:default PodIP:10.42.0.25 AgentPort:9900 AppMounts:/tel_app_mounts AppPort:8080 ManagerHost:traffic-manager.ambassador ManagerPort:8081}\"\ntime=\"2021-09-16 17:07:04.8257\" level=info msg=\"new agent secrets mount path: /var/run/secrets/kubernetes.io\"\ntime=\"2021-09-16 17:07:04.8298\" level=info msg=\"Connected to Manager v2.4.3-98-g0585bbd1-1631811285\" THREAD=/client\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/testLogDir/traffic-manager-5c69859f94-g4ntj",
    "content": "2021-09-16 17:06:51.8710 info    Logging at this level \"info\"\n2021-09-16 17:06:51.8711 info    Traffic Manager v2.4.3-98-g0585bbd1-1631811285 [pid:1]\n2021-09-16 17:06:51.9889 info    Using DNS IP from kube-dns.kube-system\n2021-09-16 17:06:51.9959 info    Using cluster domain \"cluster.local.\"\n2021-09-16 17:06:52.0282 info    Extracting service subnet 10.43.0.0/16 from create service error message\n2021-09-16 17:06:52.0572 info    Deriving subnets from podCIRs of nodes\n2021-09-16 17:06:52.0852 info    agent-injector : Mutating webhook service is listening on :8443\n2021/09/16 17:06:52 Patching synced Node fe5953af-7777-4787-accf-719601327fb6\n2021-09-16 17:07:03.4843 info    agent-injector : Injecting traffic-agent into pod echo-auto-inject-6496f77cbd-.default\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/zipDir/diff_name.log",
    "content": "logs from diff_name.log\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/zipDir/file1.log",
    "content": "logs from file1.log\n"
  },
  {
    "path": "pkg/client/cli/cmd/testdata/zipDir/file2.log",
    "content": "logs from file2.log\n"
  },
  {
    "path": "pkg/client/cli/cmd/uninstall.go",
    "content": "package cmd\n\nimport (\n\t\"errors\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\nconst allAgentsFlag = \"all-agents\"\n\ntype uninstallCommand struct {\n\tagent     bool\n\tallAgents bool\n}\n\nfunc uninstall() *cobra.Command {\n\tui := &uninstallCommand{}\n\tcmd := &cobra.Command{\n\t\tUse:   \"uninstall [flags] <workloads...>\",\n\t\tArgs:  ui.args,\n\t\tShort: \"Uninstall telepresence agents\",\n\t\tRunE:  ui.run,\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session: ann.Required,\n\t\t},\n\t\tValidArgsFunction: validWorkloads,\n\t}\n\tflags := cmd.Flags()\n\tflags.BoolVarP(&ui.allAgents, allAgentsFlag, \"a\", false, \"uninstall intercept agent on all workloads\")\n\n\t// Hidden from help but will yield a deprecation warning if used\n\tflags.BoolVarP(&ui.agent, \"agent\", \"d\", false, \"\")\n\tflags.Lookup(\"agent\").Hidden = true\n\treturn cmd\n}\n\nfunc (u *uninstallCommand) args(_ *cobra.Command, args []string) error {\n\tif len(args) > 0 {\n\t\tif u.allAgents {\n\t\t\treturn errors.New(\"--all-agents cannot be used with additional arguments\")\n\t\t}\n\t} else if !u.allAgents {\n\t\treturn errors.New(\"please specify at least one workload or use or --all-agents\")\n\t}\n\treturn nil\n}\n\n// uninstall.\nfunc (u *uninstallCommand) run(cmd *cobra.Command, args []string) error {\n\tif u.agent {\n\t\tioutil.Println(cmd.OutOrStderr(), \"--agent is deprecated (it's the default, so the flag has no effect)\")\n\t}\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\treturn err\n\t}\n\tdefer progress.Stop(cmd.Context())\n\tur := &connector.UninstallRequest{\n\t\tUninstallType: 0,\n\t}\n\tif u.allAgents {\n\t\tur.UninstallType = connector.UninstallRequest_ALL_AGENTS\n\t} else {\n\t\tur.UninstallType = connector.UninstallRequest_NAMED_AGENTS\n\t\tur.Agents = args\n\t}\n\tctx := cmd.Context()\n\t_, err := daemon.MustGetUserClient(ctx).Uninstall(ctx, ur)\n\treturn grpc.FromGRPC(err)\n}\n\nfunc validWorkloads(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// Trace level is used here, because we generally don't want to log expansion attempts\n\t// in the cli.log\n\tclog.Tracef(cmd.Context(), \"toComplete = %s, args = %v\", toComplete, args)\n\n\tall, _ := cmd.Flags().GetBool(allAgentsFlag)\n\tif all {\n\t\treturn nil, cobra.ShellCompDirectiveNoFileComp\n\t}\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\tclog.Debug(cmd.Context(), err)\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\treq := connector.ListRequest{\n\t\tFilter: connector.ListRequest_INSTALLED_AGENTS,\n\t}\n\tctx := cmd.Context()\n\n\tr, err := daemon.MustGetUserClient(ctx).List(ctx, &req)\n\tif err != nil {\n\t\tclog.Debugf(ctx, \"unable to get list of workloads with agents: %v\", err)\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\n\tlist := make([]string, 0)\n\tfor _, w := range r.Workloads {\n\t\t// only suggest strings that start with the string were autocompleting\n\t\tif strings.HasPrefix(w.Name, toComplete) && !slices.Contains(args, w.Name) {\n\t\t\tlist = append(list, w.Name)\n\t\t}\n\t}\n\treturn list, cobra.ShellCompDirectiveNoFileComp\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/usage.go",
    "content": "package cmd\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/moby/term\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n)\n\nvar CLIHelpDocumentationURL = \"https://www.telepresence.io\" //nolint:gochecknoglobals // extension point\n\nconst (\n\thelpPlain = `Telepresence can connect to a cluster and route all outbound traffic from your\nworkstation to that cluster so that software running locally can communicate\nas if it executed remotely, inside the cluster. This is achieved using the\ncommand:\n\ntelepresence connect\n\nTelepresence can also intercept traffic intended for a specific service in a\ncluster and redirect it to your local workstation:\n\ntelepresence intercept <name of service>\n\nTelepresence uses a background process, the user daemon, to manage the cluster\nsession, and a system service, the root daemon, to modify the workstation's\nnetwork and DNS so that the cluster's services become available locally.\n\nIf Telepresence was installed as a standalone binary, the system service will\nnot be present. A root daemon must then be started using sudo, which may\nresult in a password prompt.`\n\n\thelpMarkdown = `Telepresence can connect to a cluster and route all outbound traffic from your\nworkstation to that cluster so that software running locally can communicate\nas if it executed remotely, inside the cluster. This is achieved using the\ncommand:\n` + \"```bash\" + `\ntelepresence connect\n` + \"```\" + `\n\nTelepresence can also intercept traffic intended for a specific service in a\ncluster and redirect it to your local workstation:\n\n` + \"```bash\" + `\ntelepresence intercept <name of service>\n` + \"```\" + `\n\nTelepresence uses a background process, the user daemon, to manage the cluster\nsession, and a system service, the root daemon, to modify the workstation's\nnetwork and DNS so that the cluster's services become available locally.\n\nIf Telepresence was installed as a standalone binary, the system service will\nnot be present. A root daemon must then be started using sudo, which may\nresult in a password prompt.`\n\n\tusagePlain = `Usage:\n{{- if .HasAvailableSubCommands}}\n  {{.CommandPath}} [command] [flags]\n{{- else}}\n{{- if .Runnable}}\n  {{.UseLine}}\n{{- end}}\n{{- end}}\n{{- if gt (len .Aliases) 0}}\n\nAliases:\n  {{.NameAndAliases}}\n{{- end}}\n{{- if .HasExample}}\n\nExamples:\n{{.Example}}\n{{- end}}\n{{- if .HasAvailableSubCommands}}\n\nAvailable Commands:\n  {{- range .Commands}}\n    {{- if (or .IsAvailableCommand (eq .Name \"help\")) }}\n  {{rpad .Name .NamePadding }} {{.Short}}\n    {{- end}}\n  {{- end}}\n{{- end}}\n{{- if .HasAvailableLocalFlags}}\n\n{{- range flags .}}\n\n{{.Name}}:\n{{wrappedFlagUsages . | trimTrailingWhitespaces}}\n{{- end}}\n{{- end}}\n{{- if hasKubeFlags .}}\n\nKubernetes flags:\n{{kubeFlags | wrappedFlagUsages | trimTrailingWhitespaces}}{{end}}\n\nGlobal Flags:\n{{globalFlags . | wrappedFlagUsages | trimTrailingWhitespaces}}\n{{- if .HasHelpSubCommands}}\n\nAdditional help topics:\n  {{- range .Commands}}\n    {{- if .IsAdditionalHelpTopicCommand}}\n  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}\n    {{- end}}\n  {{- end}}\n{{- end}}\n{{- if .HasAvailableSubCommands}}\n\nUse \"{{.CommandPath}} [command] --help\" for more information about a command.\n\nFor complete documentation and quick-start guides, check out our website at {{ getDocumentationURL }}\n{{- end}}\n`\n\tusageMarkdown = `### Usage:\n` + \"```\" + `\n{{- if .HasAvailableSubCommands}}\n  {{.CommandPath}} [command] [flags]\n{{- else}}\n{{- if .Runnable}}\n  {{.UseLine}}\n{{- end}}\n{{- end}}\n` + \"```\" + `\n{{- if gt (len .Aliases) 0}}\n\n### Aliases:\n  {{.NameAndAliases}}\n{{- end}}\n{{- if .HasExample}}\n\n### Examples:\n` + \"```\" + `\n{{.Example}}\n` + \"```\" + `\n{{- end}}\n{{- if .HasAvailableSubCommands}}\n\n### Available Commands:\n| Command | Description |\n|---------|-------------|\n  {{- range .Commands}}\n    {{- if .IsAvailableCommand }}\n| {{commandLink .}} | {{.Short}} |\n    {{- end}}\n  {{- end}}\n{{- end}}\n{{- if .HasAvailableLocalFlags}}\n\n{{- range flags .}}\n\n### {{.Name}}:\n` + \"```\" + `\n{{wrappedFlagUsages . | trimTrailingWhitespaces}}\n` + \"```\" + `\n{{- end}}\n{{- end}}\n{{- if hasKubeFlags .}}\n\n### Kubernetes flags:\n` + \"```\" + `\n{{kubeFlags | wrappedFlagUsages | trimTrailingWhitespaces}}\n` + \"```\" + `\n{{- end}}\n\n### Global Flags:\n` + \"```\" + `\n{{globalFlags . | wrappedFlagUsages | trimTrailingWhitespaces}}\n` + \"```\" + `\n{{- if .HasHelpSubCommands}}\n\n### Additional help topics:\n` + \"```\" + `\n  {{- range .Commands}}\n    {{- if .IsAdditionalHelpTopicCommand}}\n  {{rpad .CommandPath .CommandPathPadding}} {{.Short}}\n    {{- end}}\n  {{- end}}\n` + \"```\" + `\n{{- end}}\n{{- if .HasAvailableSubCommands}}\n\nUse ` + \"`{{.CommandPath}} [command] --help`\" + ` for more information about a command.\n{{- end}}\n`\n)\n\nfunc flagEqual(a, b *pflag.Flag) bool {\n\tif a == b {\n\t\treturn true\n\t}\n\tif a == nil || b == nil {\n\t\treturn false\n\t}\n\treturn a.Name == b.Name && a.Usage == b.Usage && a.Hidden == b.Hidden\n}\n\nfunc localFlags(cmd *cobra.Command, exclude ...*pflag.FlagSet) []*pflag.FlagSet {\n\tngFlags := pflag.NewFlagSet(\"Flags\", pflag.ContinueOnError)\n\textra := flags.GetFlagSets(cmd.Context())\n\tif len(extra) > 0 {\n\t\texclude = append(exclude, extra...)\n\t}\n\tcmd.Flags().VisitAll(func(flag *pflag.Flag) {\n\t\tfor _, ex := range exclude {\n\t\t\tif flagEqual(flag, ex.Lookup(flag.Name)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tngFlags.AddFlag(flag)\n\t})\n\treturn append([]*pflag.FlagSet{ngFlags}, extra...)\n}\n\nfunc kubeFlags() *pflag.FlagSet {\n\tpflag.NewFlagSet(\"Kubernetes flags\", 0)\n\tkubeConfig := genericclioptions.NewConfigFlags(false)\n\tkubeConfig.Namespace = nil // \"connect\", don't take --namespace\n\tkfs := pflag.NewFlagSet(\"Kubernetes flags\", 0)\n\tkubeConfig.AddFlags(kfs)\n\treturn kfs\n}\n\nfunc hasKubeFlags(cmd *cobra.Command) bool {\n\tyep := true\n\tflags := cmd.Flags()\n\tkubeFlags().VisitAll(func(flag *pflag.Flag) {\n\t\tif yep && !flagEqual(flag, flags.Lookup(flag.Name)) {\n\t\t\tyep = false\n\t\t}\n\t})\n\treturn yep\n}\n\nfunc commandLink(cmd *cobra.Command) string {\n\treturn fmt.Sprintf(\"[%s](%s)\", cmd.Name(), strings.ReplaceAll(cmd.CommandPath(), \" \", \"_\"))\n}\n\nfunc addUsageTemplate(cmd *cobra.Command, markdown bool) {\n\tcobra.AddTemplateFunc(\"globalFlags\", func(cmd *cobra.Command) *pflag.FlagSet {\n\t\treturn global.Flags(cmd.Context(), hasKubeFlags(cmd), markdown)\n\t})\n\tcobra.AddTemplateFunc(\"flags\", func(cmd *cobra.Command) []*pflag.FlagSet {\n\t\treturn localFlags(cmd, kubeFlags(), global.Flags(cmd.Context(), hasKubeFlags(cmd), markdown))\n\t})\n\tcobra.AddTemplateFunc(\"hasKubeFlags\", hasKubeFlags)\n\tcobra.AddTemplateFunc(\"kubeFlags\", kubeFlags)\n\tcobra.AddTemplateFunc(\"commandLink\", commandLink)\n\tcobra.AddTemplateFunc(\"wrappedFlagUsages\", func(flags *pflag.FlagSet) string {\n\t\tcols := 0\n\t\tif !markdown {\n\t\t\t// This is based off of what Docker does (github.com/docker/cli/cli/cobra.go), but is\n\t\t\t// adjusted\n\t\t\t//  1. to take a pflag.FlagSet instead of a cobra.interceptCmd, so that we can have flag groups, and\n\t\t\t//  2. to correct for the ways that Docker upsets me.\n\n\t\t\t// Obey COLUMNS if the shell or user sets it.  (Docker doesn't do this.)\n\t\t\tvar err error\n\t\t\tcols, err = strconv.Atoi(os.Getenv(\"COLUMNS\"))\n\t\t\tif err != nil {\n\t\t\t\t// Try to detect the size of the stdout file descriptor.  (Docker checks stdin, not stdout.)\n\t\t\t\tif ws, err := term.GetWinsize(1); err != nil {\n\t\t\t\t\t// If stdout is a terminal, but we were unable to get its size (I'm not sure how that can\n\t\t\t\t\t// happen), then fall back to assuming 80.  If stdout isn't a terminal, then we leave cols\n\t\t\t\t\t// as 0, meaning \"don't wrap it\".  (Docker wraps it even if stdout isn't a terminal.)\n\t\t\t\t\tif term.IsTerminal(1) {\n\t\t\t\t\t\tcols = 80\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tcols = int(ws.Width)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn flags.FlagUsagesWrapped(cols)\n\t})\n\tcobra.AddTemplateFunc(\"getDocumentationURL\", func() string {\n\t\treturn CLIHelpDocumentationURL\n\t})\n\n\t// Set a usage template that is derived from the default but replaces the \"Available Commands\"\n\t// section with the commandGroups() from the given command\n\ttpl := usagePlain\n\tif markdown {\n\t\ttpl = usageMarkdown\n\t}\n\tcmd.SetUsageTemplate(tpl)\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/version.go",
    "content": "package cmd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\tdaemonRpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nfunc versionCmd() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:  \"version\",\n\t\tArgs: cobra.NoArgs,\n\n\t\tShort: \"Show version\",\n\t\tRunE:  printVersion,\n\t\tAnnotations: map[string]string{\n\t\t\tann.UserDaemon:        ann.Optional,\n\t\t\tann.UpdateCheckFormat: ann.Tel2,\n\t\t},\n\t\tValidArgsFunction: cobra.NoFileCompletions,\n\t}\n}\n\nfunc addDaemonVersions(ctx context.Context, kvf *ioutil.KeyValueFormatter) {\n\thasRootDaemon := true\n\tuserD := daemon.GetUserClient(ctx)\n\tif userD != nil {\n\t\thasRootDaemon = !(proc.IsAdmin() || userD.Containerized())\n\t}\n\n\tif hasRootDaemon {\n\t\tversion, err := daemonVersion(ctx)\n\t\tswitch {\n\t\tcase err == nil:\n\t\t\tkvf.Add(version.Name, version.Version)\n\t\tcase errors.Is(err, daemon.ErrNoRootDaemon):\n\t\t\tkvf.Add(\"Root Daemon\", \"not running\")\n\t\tdefault:\n\t\t\tkvf.Add(\"Root Daemon\", fmt.Sprintf(\"error: %v\", err))\n\t\t}\n\t}\n\n\tif userD != nil {\n\t\tkvf.Add(userD.Name(), \"v\"+userD.Semver().String())\n\t\tvi, err := managerVersion(ctx)\n\t\tswitch {\n\t\tcase err == nil:\n\t\t\tkvf.Add(vi.Name, vi.Version)\n\t\t\taf, err := trafficAgentFQN(ctx)\n\t\t\tswitch status.Code(err) {\n\t\t\tcase codes.OK:\n\t\t\t\tkvf.Add(\"Traffic Agent\", af.FQN)\n\t\t\tcase codes.Unimplemented:\n\t\t\t\tkvf.Add(\"Traffic Agent\", \"not reported by traffic-manager\")\n\t\t\tcase codes.Unavailable:\n\t\t\t\tkvf.Add(\"Traffic Agent\", \"not currently available\")\n\t\t\tdefault:\n\t\t\t\tkvf.Add(\"Traffic Agent\", fmt.Sprintf(\"error: %v\", err))\n\t\t\t}\n\t\tcase status.Code(err) == codes.Unavailable:\n\t\t\tkvf.Add(\"Traffic Manager\", \"not connected\")\n\t\tdefault:\n\t\t\tkvf.Add(\"Traffic Manager\", fmt.Sprintf(\"error: %v\", err))\n\t\t}\n\t} else {\n\t\tkvf.Add(\"User Daemon\", \"not running\")\n\t}\n}\n\nfunc printVersion(cmd *cobra.Command, _ []string) error {\n\tkvf := ioutil.DefaultKeyValueFormatter()\n\tkvf.Add(client.DisplayName, client.Version())\n\n\tvar mdErr daemon.MultipleDaemonsError\n\terr := connect.InitCommand(cmd)\n\tif err != nil {\n\t\tif !errors.As(err, &mdErr) {\n\t\t\treturn err\n\t\t}\n\t}\n\tdefer progress.Stop(cmd.Context())\n\tctx := cmd.Context()\n\n\tif len(mdErr) > 0 {\n\t\tfor _, info := range mdErr {\n\t\t\tsubKvf := &ioutil.KeyValueFormatter{\n\t\t\t\tIndent:    kvf.Indent,\n\t\t\t\tSeparator: kvf.Separator,\n\t\t\t}\n\t\t\tudCtx, err := connect.ExistingDaemon(ctx, info)\n\t\t\tif err != nil {\n\t\t\t\tsubKvf.Add(\"User Daemon\", fmt.Sprintf(\"error: %v\", err))\n\t\t\t}\n\t\t\taddDaemonVersions(udCtx, subKvf)\n\t\t\tud := daemon.MustGetUserClient(udCtx)\n\t\t\tkvf.Add(\"Connection \"+ud.DaemonID().Name, \"\\n\"+subKvf.String())\n\t\t\t_ = ud.Close()\n\t\t}\n\t} else {\n\t\taddDaemonVersions(ctx, kvf)\n\t}\n\n\tkvf.Println(cmd.OutOrStdout())\n\treturn nil\n}\n\nfunc daemonVersion(ctx context.Context) (*common.VersionInfo, error) {\n\tif conn, err := daemon.DialRootDaemon(ctx, false); err == nil {\n\t\tdefer conn.Close()\n\t\treturn daemonRpc.NewDaemonClient(conn).Version(ctx, &empty.Empty{})\n\t}\n\treturn nil, daemon.ErrNoRootDaemon\n}\n\nfunc managerVersion(ctx context.Context) (*common.VersionInfo, error) {\n\tif s := daemon.GetSession(ctx); s != nil {\n\t\tmv := s.Info.ManagerVersion\n\t\treturn &common.VersionInfo{\n\t\t\tVersion: mv.Version,\n\t\t\tName:    mv.Name,\n\t\t}, nil\n\t}\n\tif userD := daemon.GetUserClient(ctx); userD != nil {\n\t\tmv, err := userD.TrafficManagerVersion(ctx, &empty.Empty{})\n\t\treturn mv, tpGrpc.FromGRPC(err)\n\t}\n\treturn nil, daemon.ErrNoUserDaemon\n}\n\nfunc trafficAgentFQN(ctx context.Context) (*manager.AgentImageFQN, error) {\n\tif userD := daemon.GetUserClient(ctx); userD != nil {\n\t\tai, err := userD.AgentImageFQN(ctx, &empty.Empty{})\n\t\treturn ai, tpGrpc.FromGRPC(err)\n\t}\n\treturn nil, daemon.ErrNoUserDaemon\n}\n"
  },
  {
    "path": "pkg/client/cli/cmd/wiretap.go",
    "content": "package cmd\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n)\n\nfunc wiretapCmd() *cobra.Command {\n\tic := &intercept.Command{\n\t\tWiretap: true,\n\t}\n\tcmd := &cobra.Command{\n\t\tUse:   \"wiretap [flags] <wiretap_base_name> [-- <command with arguments...>]\",\n\t\tArgs:  cobra.MinimumNArgs(1),\n\t\tShort: \"Wiretap a Service\",\n\t\tAnnotations: map[string]string{\n\t\t\tann.Session:           ann.Required,\n\t\t\tann.UpdateCheckFormat: ann.Tel2,\n\t\t},\n\t\tSilenceUsage:      true,\n\t\tSilenceErrors:     true,\n\t\tRunE:              ic.Run,\n\t\tValidArgsFunction: intercept.ValidArgs,\n\t}\n\tic.AddInterceptFlags(cmd)\n\treturn cmd\n}\n"
  },
  {
    "path": "pkg/client/cli/connect/connector.go",
    "content": "package connect\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n\t\"k8s.io/client-go/kubernetes\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\tdaemonRpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker/teleroute\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n//nolint:gochecknoglobals // extension point\nvar QuitDaemonFuncs = []func(context.Context){\n\tquitHostConnector, quitDockerDaemons,\n}\n\n// maybeComposeDown performs a `docker compose down -f <compose file>` and also deletes `<compose file>` if it exists.\n// A full docker compose down is necessary because the created containers are dependent on the network represented by\n// the connection that is about to close.\nfunc maybeComposeDown(ctx context.Context, info *daemon.Info) {\n\tif info.ComposeFile == \"\" {\n\t\treturn\n\t}\n\tdefer func() {\n\t\terr := os.Remove(info.ComposeFile)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t}\n\t}()\n\tprogress.Stop(ctx)\n\terr := proc.StdCommand(ctx, docker.Exe, \"compose\", \"--file\", info.ComposeFile, \"down\", \"--remove-orphans\", \"--volumes\").Run()\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t}\n\tprogress.Start(ctx, \"Quitting\")\n}\n\nfunc findHostConnectorInfo(ctx context.Context) (*daemon.Info, error) {\n\til := daemon.NewUserInfoLoader(ctx)\n\tinfos, err := il.LoadInfos()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, info := range infos {\n\t\tif !info.InDocker() {\n\t\t\treturn info, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"unable to find host connector info: %w\", fs.ErrNotExist)\n}\n\nfunc quitHostConnector(ctx context.Context) {\n\tinfo, err := findHostConnectorInfo(ctx)\n\tif err == nil {\n\t\tctx, err = ExistingDaemon(ctx, info)\n\t}\n\tprogress.Working(ctx, \"Quitting\")\n\tvar errs error\n\trootWillContinue := false\n\tif err != nil {\n\t\tif !(errors.Is(err, fs.ErrNotExist) || errors.Is(err, daemon.ErrNoUserDaemon)) {\n\t\t\terrs = errors.Join(errs, err)\n\t\t}\n\t} else {\n\t\tud := daemon.MustGetUserClient(ctx)\n\t\tqr, err := ud.Quit(ctx, &emptypb.Empty{})\n\t\tif err != nil {\n\t\t\terrs = errors.Join(errs, tpGrpc.FromGRPC(err))\n\t\t} else {\n\t\t\trootWillContinue = qr.RootDaemonWillContinue\n\t\t}\n\t\t_ = ud.Close()\n\t}\n\tif !rootWillContinue {\n\t\t// User daemon is responsible for killing the root daemon, but we kill it here too to cater for\n\t\t// the fact that the user daemon might have been killed ungracefully.\n\t\tif conn, err := daemon.DialRootDaemon(ctx, false); err == nil {\n\t\t\tif _, err = daemonRpc.NewDaemonClient(conn).Quit(ctx, &emptypb.Empty{}); err != nil {\n\t\t\t\terrs = errors.Join(errs, err)\n\t\t\t}\n\t\t\t_ = conn.Close()\n\t\t}\n\t}\n\tif errs != nil {\n\t\t_ = progress.MaybeWriteError(ctx, errs)\n\t}\n\tprogress.PrintDone(ctx, \"Quit\")\n}\n\nfunc quitDockerDaemons(ctx context.Context) {\n\til := daemon.NewUserInfoLoader(ctx)\n\tinfos, err := il.LoadInfos()\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\treturn\n\t}\n\tfor _, info := range infos {\n\t\tmaybeComposeDown(ctx, info)\n\t\tctx := progress.WithEventId(ctx, info.DaemonID().Name)\n\t\tprogress.Working(ctx, \"Quitting\")\n\t\tudCtx, err := ExistingDaemon(ctx, info)\n\t\tif err != nil {\n\t\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\t\tprogress.Error(ctx, err.Error())\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tud := daemon.MustGetUserClient(udCtx)\n\t\t_, _ = ud.Quit(ctx, &emptypb.Empty{})\n\t\t_ = ud.Close()\n\t\tmaybeDeleteNetwork(ctx, ud.DaemonInfo())\n\t\tprogress.PrintDone(ctx, \"Quit\")\n\t}\n}\n\nfunc EnsureUserDaemon(ctx context.Context, required bool) (rc context.Context, err error) {\n\tcr := daemon.MustGetRequest(ctx)\n\tdaemonID, err := daemon.IdentifierFromFlags(ctx, cr.Name, cr.KubeFlags, cr.KubeconfigData, cr.Docker)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\n\tctx = progress.WithEventId(ctx, daemonID.Name)\n\tlaunched := false\n\tdefer func() {\n\t\tif err == nil && required && !(proc.IsAdmin() || daemon.MustGetUserClient(rc).Containerized()) {\n\t\t\t// The RootDaemon must be started if the UserDaemon was started\n\t\t\terr = EnsureRootDaemonRunning(ctx)\n\t\t}\n\t\tif err != nil && !(errors.Is(err, daemon.ErrNoUserDaemon) && !required) {\n\t\t\terr = progress.MaybeWriteError(ctx, err)\n\t\t} else if launched {\n\t\t\tprogress.PrintDone(ctx, \"Launched Daemon\")\n\t\t}\n\t}()\n\n\tif daemon.GetUserClient(ctx) != nil {\n\t\treturn ctx, nil\n\t}\n\trc, launched, err = findOrLaunchConnectorDaemon(ctx, daemonID, client.GetExe(ctx), required)\n\treturn rc, err\n}\n\nfunc EnsureSession(ctx context.Context, useLine string, required bool) (context.Context, error) {\n\tif daemon.GetSession(ctx) != nil {\n\t\treturn ctx, nil\n\t}\n\n\trq := daemon.MustGetRequest(ctx)\n\ts, err := connectSession(ctx, useLine, rq, required)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tif s == nil {\n\t\treturn ctx, nil\n\t}\n\n\tif s.Started && s.Containerized() {\n\t\trootCfg, err := s.GetRootClientConfig()\n\t\tif err != nil {\n\t\t\treturn ctx, err\n\t\t}\n\t\tif len(rootCfg.Routing().Subnets) > 0 {\n\t\t\terr = createTelerouteNetwork(ctx, s.DaemonInfo())\n\t\t\tif err != nil {\n\t\t\t\treturn ctx, err\n\t\t\t}\n\t\t}\n\t}\n\n\tfor _, pm := range rq.LocalReroutes {\n\t\terr = ResolveLocalReroute(ctx, s, pm)\n\t\tif err != nil {\n\t\t\treturn ctx, err\n\t\t}\n\t}\n\n\tfor _, pm := range rq.RemoteReroutes {\n\t\terr = ResolveRemoteReroute(ctx, s, pm)\n\t\tif err != nil {\n\t\t\treturn ctx, err\n\t\t}\n\t}\n\treturn daemon.WithSession(ctx, s), nil\n}\n\nfunc ResolveLocalReroute(ctx context.Context, ds *daemon.Session, pm string) (err error) {\n\tix := strings.IndexByte(pm, ':')\n\tif ix < 1 {\n\t\treturn fmt.Errorf(\"invalid port mapping %s\", pm)\n\t}\n\tlocalPort, err := types.ParsePortAndProto(pm[:ix])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid port mapping %s: local port %w\", pm, err)\n\t}\n\thostPort, err := resolveHostPort(ctx, ds, localPort.Proto, pm[ix+1:])\n\tif err != nil {\n\t\treturn err\n\t}\n\thpb, err := hostPort.MarshalBinary()\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = ds.RerouteLocalPort(ctx, &daemonRpc.ReroutePortRequest{\n\t\tDstHostPort: hpb,\n\t\tSrcPort:     uint32(localPort.Port),\n\t})\n\treturn tpGrpc.FromGRPC(err)\n}\n\nfunc ResolveRemoteReroute(ctx context.Context, ds *daemon.Session, pm string) (err error) {\n\tix := strings.LastIndexByte(pm, ':')\n\tif ix < 3 {\n\t\treturn fmt.Errorf(\"invalid port mapping %s\", pm)\n\t}\n\tnewPort, err := types.ParsePortAndProto(pm[ix+1:])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid port mapping %s: new port %w\", pm, err)\n\t}\n\thostPort, err := resolveHostPort(ctx, ds, newPort.Proto, pm[:ix])\n\tif err != nil {\n\t\treturn err\n\t}\n\thpb, err := hostPort.MarshalBinary()\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = ds.RerouteRemotePort(ctx, &daemonRpc.ReroutePortRequest{\n\t\tDstHostPort: hpb,\n\t\tSrcPort:     uint32(newPort.Port),\n\t})\n\treturn tpGrpc.FromGRPC(err)\n}\n\nfunc resolveHostPort(ctx context.Context, ds *daemon.Session, proto types.Proto, hostPortStr string) (hostPort types.AddrPortProto, err error) {\n\thostPort.Proto = proto\n\thostPort.AddrPort, err = netip.ParseAddrPort(hostPortStr)\n\tif err == nil {\n\t\treturn hostPort, nil\n\t}\n\t// Resolve host and port name\n\tix := strings.LastIndexByte(hostPortStr, ':')\n\tif ix < 1 {\n\t\treturn hostPort, fmt.Errorf(\"invalid port mapping %s\", hostPortStr)\n\t}\n\tportStr := hostPortStr[ix+1:]\n\tif hostPort.Proto != types.ProtoTCP {\n\t\tportStr = fmt.Sprintf(\"%s%c%s\", portStr, types.ProtoSeparator, hostPort.Proto)\n\t}\n\trsp, err := ds.ResolvePort(ctx, &daemonRpc.ResolvePortRequest{\n\t\tHost: hostPortStr[:ix],\n\t\tPort: portStr,\n\t})\n\tif err != nil {\n\t\treturn hostPort, tpGrpc.FromGRPC(err)\n\t}\n\terr = hostPort.UnmarshalBinary(rsp.HostPort)\n\treturn hostPort, err\n}\n\nfunc ExistingDaemon(ctx context.Context, info *daemon.Info) (context.Context, error) {\n\tvar conn *grpc.ClientConn\n\tvar err error\n\tif info.InDocker() {\n\t\t// The host relies on that the daemon has exposed a port to localhost\n\t\tconn, err = docker.ConnectDaemon(ctx, info)\n\t} else {\n\t\tconn, err = daemon.DialUserDaemon(ctx, false)\n\t}\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\treturn newUserDaemon(ctx, conn, info)\n}\n\n// Quit shuts down all daemons.\nfunc Quit(ctx context.Context) {\n\tprogress.Start(ctx, \"Quitting\")\n\tdefer progress.Stop(ctx)\n\tfor _, quitFunc := range QuitDaemonFuncs {\n\t\tquitFunc(ctx)\n\t}\n}\n\n// Disconnect disconnects from a session in the user daemon.\nfunc Disconnect(ctx context.Context) {\n\tprogress.Start(ctx, \"Disconnecting\")\n\tdefer progress.Stop(ctx)\n\tif ud := daemon.GetUserClient(ctx); ud == nil {\n\t\tprogress.PrintDone(progress.WithEventId(ctx, \"daemon\"), \"Not connected\")\n\t} else {\n\t\tid := ud.DaemonID()\n\t\tif id == nil {\n\t\t\tprogress.PrintDone(progress.WithEventId(ctx, \"daemon\"), \"Not connected\")\n\t\t\treturn\n\t\t}\n\t\tmaybeComposeDown(ctx, ud.DaemonInfo())\n\t\tctx = progress.WithEventId(ctx, id.Name)\n\t\tprogress.Working(ctx, \"Disconnecting\")\n\t\t_, err := ud.Disconnect(ctx, &emptypb.Empty{})\n\t\tswitch {\n\t\tcase err == nil:\n\t\t\tprogress.PrintDone(ctx, \"Disconnected\")\n\t\t\tmaybeDeleteNetwork(ctx, ud.DaemonInfo())\n\t\tcase status.Code(err) == codes.Unavailable:\n\t\t\tprogress.PrintDone(ctx, \"Not connected\")\n\t\tdefault:\n\t\t\t_ = progress.MaybeWriteError(ctx, fmt.Errorf(\"failed to disconnect: %w\", tpGrpc.FromGRPC(err)))\n\t\t}\n\t}\n}\n\nfunc RunConnect(cmd *cobra.Command, args []string) error {\n\tif err := InitCommand(cmd); err != nil {\n\t\treturn err\n\t}\n\tif len(args) == 0 {\n\t\treturn nil\n\t}\n\tctx := cmd.Context()\n\tif daemon.MustGetSession(ctx).Started {\n\t\tdefer Disconnect(ctx)\n\t}\n\treturn proc.Run(dos.WithStdio(ctx, cmd), nil, args[0], args[1:]...)\n}\n\n// DiscoverDaemon searches the daemon cache for an entry corresponding to the given name. A connection\n// to that daemon is returned if such an entry is found.\nfunc DiscoverDaemon(ctx context.Context, match *regexp.Regexp, daemonID *daemon.Identifier) (context.Context, error) {\n\tcr := daemon.MustGetRequest(ctx)\n\tif match == nil && !cr.Implicit {\n\t\tmatch = regexp.MustCompile(`\\A` + regexp.QuoteMeta(daemonID.Name) + `\\z`)\n\t}\n\til := daemon.NewUserInfoLoader(ctx)\n\tinfo, err := il.LoadMatchingInfo(match)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tif len(cr.ExposedPorts) > 0 && !slices.Equal(info.ExposedPorts, cr.ExposedPorts) {\n\t\treturn ctx, errcat.User.New(\"exposed ports differ. Please quit and reconnect\")\n\t}\n\treturn ExistingDaemon(ctx, info)\n}\n\nfunc launchDockerDaemon(ctx context.Context, daemonID *daemon.Identifier, cr *daemon.Request) (context.Context, error) {\n\t// Ensure that the logfile is present before the daemon starts so that it isn't created with\n\t// permissions from the docker container.\n\tlogDir := filelocation.AppUserLogDir(ctx)\n\tlogFile := filepath.Join(logDir, \"connector.log\")\n\tif _, err := os.Stat(logFile); err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn ctx, err\n\t\t}\n\t\tfh, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0o666)\n\t\tif err != nil {\n\t\t\treturn ctx, err\n\t\t}\n\t\t_ = fh.Close()\n\t}\n\t_, err := docker.EnsureNetworkPlugin(ctx)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\n\t// An initialized kubernetes interface is required by LaunchDaemon, because it is necessary\n\t// when checking if the containerized daemon is connecting to a k3s control plane node.\n\tkc, err := k8s.NewKubeconfig(ctx, false, cr.KubeFlags, cr.ManagerNamespace, cr.KubeconfigData)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tvar ki *kubernetes.Clientset\n\tki, err = kubernetes.NewForConfig(kc.RestConfig)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tctx = kc.Context\n\tinfo, conn, err := docker.LaunchDaemon(k8sapi.WithK8sInterface(ctx, ki), daemonID)\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\treturn newUserDaemon(ctx, conn, info)\n}\n\nfunc launchHostDaemon(ctx context.Context, daemonID *daemon.Identifier, connectorDaemon string, cr *daemon.Request) (context.Context, error) {\n\targs := []string{connectorDaemon, client.UserDaemonName, \"--\" + global.FlagConfig, client.GetConfigFile(ctx)}\n\tif cr.UserDaemonProfilingPort > 0 {\n\t\targs = append(args, \"--pprof\", strconv.Itoa(int(cr.UserDaemonProfilingPort)))\n\t}\n\tif proc.IsAdmin() {\n\t\t// No use having multiple daemons when running as root.\n\t\targs = append(args, \"--embed-network\")\n\t}\n\tclog.Debugf(ctx, \"Creating daemon info file %s (runs on host, or both CLI and daemon runs in container)\", daemonID.Name)\n\tfp, err := ioutil.FreePortsTCP(1)\n\tif err != nil {\n\t\treturn ctx, errcat.NoDaemonLogs.New(err)\n\t}\n\tinfo := &daemon.Info{\n\t\tDaemonPort:   fp[0].Port(),\n\t\tName:         daemonID.Name,\n\t\tKubeContext:  daemonID.KubeContext,\n\t\tNamespace:    daemonID.Namespace,\n\t\tExposedPorts: cr.ExposedPorts,\n\t\tHostname:     cr.Hostname,\n\t}\n\targs = append(args, \"--address\", fmt.Sprintf(\":%d\", info.DaemonPort))\n\tfn := daemonID.InfoFileName()\n\til := daemon.NewUserInfoLoader(ctx)\n\terr = il.SaveInfo(info, fn)\n\tif err != nil {\n\t\treturn ctx, errcat.NoDaemonLogs.New(err)\n\t}\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tfile := daemonID.InfoFileName()\n\t\t\tclog.Debugf(ctx, \"Deleting daemon info %s due to launch error: %v\", file, err)\n\t\t\t_ = il.DeleteInfo(file)\n\t\t}\n\t}()\n\n\tif err = proc.StartInBackground(false, args...); err != nil {\n\t\treturn ctx, errcat.NoDaemonLogs.Errorf(err, \"failed to launch the connector service\")\n\t}\n\tconn, err := il.DialDaemon(ctx, true)\n\tif err != nil {\n\t\treturn ctx, errcat.NoDaemonLogs.New(err)\n\t}\n\treturn newUserDaemon(ctx, conn, info)\n}\n\nfunc findOrLaunchConnectorDaemon(ctx context.Context, daemonID *daemon.Identifier, connectorDaemon string, required bool) (context.Context, bool, error) {\n\tcr := daemon.MustGetRequest(ctx)\n\n\t// Try dialing the host daemon using the well-known socket.\n\tctx, err := DiscoverDaemon(ctx, cr.Use, daemonID)\n\tif err == nil {\n\t\tud := daemon.MustGetUserClient(ctx)\n\t\tif ud.Containerized() {\n\t\t\tcr.Docker = true\n\t\t}\n\t\tif ud.Containerized() == cr.Docker {\n\t\t\treturn ctx, false, nil\n\t\t}\n\t\t// A daemon running on the host does not fulfill a request for a containerized daemon. They can\n\t\t// coexist though.\n\t\terr = os.ErrNotExist\n\t}\n\tif !errors.Is(err, os.ErrNotExist) {\n\t\treturn ctx, false, errcat.NoDaemonLogs.New(err)\n\t}\n\tif !required {\n\t\treturn ctx, false, daemon.ErrNoUserDaemon\n\t}\n\tctx = progress.WithEventId(ctx, daemonID.Name)\n\tprogress.Working(ctx, \"Launching Daemon\")\n\n\tif err = ensureAppUserCacheDirs(ctx); err != nil {\n\t\treturn ctx, false, err\n\t}\n\tif err = ensureAppUserConfigDir(ctx); err != nil {\n\t\treturn ctx, false, err\n\t}\n\n\tif cr.Docker {\n\t\tif client.GetConfig(ctx).Intercept().UseFtp {\n\t\t\terr = errcat.Silent.New(\"FTP is not supported when using Docker. Please set intercept.useFtp=false in your config.yml and try again.\")\n\t\t\tprogress.Error(ctx, err)\n\t\t\treturn ctx, false, err\n\t\t}\n\t\tctx, err = launchDockerDaemon(ctx, daemonID, cr)\n\t} else {\n\t\tctx, err = launchHostDaemon(ctx, daemonID, connectorDaemon, cr)\n\t}\n\treturn ctx, err == nil, err\n}\n\n// getConnectorVersion is the first call to the user daemon, so a backoff is used here to trap errors\n// caused during the initial state change of the gRPC connection.\nfunc getConnectorVersion(ctx context.Context, cc connector.ConnectorClient) (*common.VersionInfo, error) {\n\tb := backoff.NewExponentialBackOff(\n\t\tbackoff.WithMaxElapsedTime(3*time.Second),\n\t\tbackoff.WithInitialInterval(50*time.Millisecond),\n\t\tbackoff.WithMaxInterval(time.Second))\n\tvar vi *common.VersionInfo\n\terr := backoff.Retry(func() (err error) {\n\t\tquick, cancel := context.WithTimeout(ctx, 50*time.Millisecond) // This is a local call. Should be quick.\n\t\tdefer cancel()\n\t\tvi, err = cc.Version(quick, &emptypb.Empty{})\n\t\treturn tpGrpc.FromGRPC(err)\n\t}, backoff.WithContext(b, ctx))\n\treturn vi, err\n}\n\nfunc newUserDaemon(ctx context.Context, conn *grpc.ClientConn, info *daemon.Info) (context.Context, error) {\n\tvi, err := getConnectorVersion(ctx, connector.NewConnectorClient(conn))\n\tif err != nil {\n\t\treturn ctx, err\n\t}\n\tv, err := semver.Parse(strings.TrimPrefix(vi.Version, \"v\"))\n\tif err != nil {\n\t\treturn ctx, fmt.Errorf(\"unable to parse version obtained from connector daemon: %w\", err)\n\t}\n\tctx = daemon.WithUserClient(ctx, daemon.NewUserClientFunc(conn, info, v, vi.Name, vi.Executable))\n\tctx = progress.WithEventId(ctx, info.Name)\n\treturn ctx, nil\n}\n\nfunc ensureDaemonVersion(ctx context.Context) error {\n\t// Ensure that the already running daemon has the correct version\n\treturn versionCheck(ctx, client.GetExe(ctx))\n}\n\n// warn if the version diff between cli and manager is > 3 or if there's an OSS/Enterprise mismatch.\nfunc warnMngrVersion(ctx context.Context, ci *connector.ConnectInfo) error {\n\tmv := ci.ManagerVersion\n\n\t// remove leading v from semver\n\tmSemver, err := semver.Parse(strings.TrimPrefix(mv.Version, \"v\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcliSemver := client.Semver()\n\n\tvar diff uint64\n\tif cliSemver.Minor > mSemver.Minor {\n\t\tdiff = cliSemver.Minor - mSemver.Minor\n\t} else {\n\t\tdiff = mSemver.Minor - cliSemver.Minor\n\t}\n\n\tconst maxDiff = uint64(3)\n\tif diff > maxDiff {\n\t\tprogress.Warningf(ctx,\n\t\t\t\"The Traffic Manager version (%s) is more than %v minor versions diff from client version (%s), please consider upgrading.\",\n\t\t\tmv.Version, maxDiff, client.Version())\n\t} else if diff > 0 {\n\t\tclog.Debugf(ctx, \"Diff between client and manager minor versions: %d\", diff)\n\t}\n\n\tcv := ci.Version\n\tif strings.HasPrefix(cv.Name, \"OSS \") && !strings.HasPrefix(mv.Name, \"OSS \") {\n\t\tprogress.Warningf(ctx,\n\t\t\t\"You are using the OSS client %s to connect to an enterprise traffic manager %s. Please consider installing an\\n\"+\n\t\t\t\t\"enterprise client from getambassador.io, or use \\\"telepresence helm install\\\" to install an OSS traffic-manager.\",\n\t\t\tcv.Version,\n\t\t\tmv.Version)\n\t}\n\treturn nil\n}\n\nfunc connectResult(ctx context.Context, ci *connector.ConnectInfo, withProgress bool) *daemon.Session {\n\terr := warnMngrVersion(ctx, ci)\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t}\n\tif withProgress {\n\t\tprogress.PrintDonef(ctx, \"Connected to context %s, namespace %s (%s)\", ci.ClusterContext, ci.Namespace, ci.ClusterServer)\n\t}\n\treturn &daemon.Session{Info: ci, Started: ci.Initial}\n}\n\nfunc connectSession(ctx context.Context, useLine string, request *daemon.Request, required bool) (session *daemon.Session, err error) {\n\tuserD := daemon.MustGetUserClient(ctx)\n\tvar ci *connector.ConnectInfo\n\tdefer func() {\n\t\tif ci != nil {\n\t\t\trequest.KubeFlags = ci.KubeFlags\n\t\t\trequest.ManagerNamespace = ci.ManagerNamespace\n\t\t\trequest.Name = ci.ConnectionName\n\n\t\t\tuserD.SetConnectionInfo(ci.ConnectionName, ci.ClusterContext, ci.Namespace)\n\t\t}\n\t\tif session != nil {\n\t\t\tsession.UserClient = userD\n\t\t}\n\t}()\n\n\timplicitConnect := false\n\tif request.Implicit {\n\t\t// implicit calls use the current Status instead of passing flags and mapped namespaces.\n\t\tci, err = userD.Status(ctx, &emptypb.Empty{})\n\t\tif err == nil && ci.ManagerVersion == nil {\n\t\t\t// If the manager version is nil, the user daemon is not connected. This is the same\n\t\t\t// as it being unavailable when the request is implicit.\n\t\t\terr = status.Errorf(codes.Unavailable, \"user daemon is not connected\")\n\t\t}\n\t\tif err == nil {\n\t\t\treturn connectResult(ctx, ci, false), nil\n\t\t}\n\t\tif status.Code(err) != codes.Unavailable {\n\t\t\treturn nil, tpGrpc.FromGRPC(err)\n\t\t}\n\t\terr = nil\n\t\tif required {\n\t\t\timplicitConnect = true\n\t\t}\n\t}\n\n\tif !required {\n\t\treturn nil, nil\n\t}\n\n\tdaemonID := userD.DaemonID()\n\tprogress.Workingf(ctx, \"Connecting to context %s, namespace %s\", daemonID.KubeContext, daemonID.Namespace)\n\tif implicitConnect {\n\t\tprogress.Warningf(ctx,\n\t\t\t`Warning: You are executing the %q command without a preceding \"telepresence connect\", causing an implicit `+\n\t\t\t\t\"connect to namespace %q. The implicit connect behavior is deprecated and will be removed in a future release.\",\n\t\t\tuseLine, daemonID.Namespace)\n\t}\n\tif ci, err = userD.Connect(ctx, request.ConnectRequest); err != nil {\n\t\tif !userD.Containerized() {\n\t\t\tfile := userD.DaemonID().InfoFileName()\n\t\t\tclog.Debugf(ctx, \"Deleting daemon info %s due to connect error: %v\", file, err)\n\t\t\t_ = daemon.NewUserInfoLoader(ctx).DeleteInfo(file)\n\t\t}\n\t\treturn nil, tpGrpc.FromGRPC(err)\n\t}\n\treturn connectResult(ctx, ci, true), nil\n}\n\nfunc createTelerouteNetwork(ctx context.Context, info *daemon.Info) error {\n\t// Make an attempt to create the network with IPv6 enabled. This will fail unless the user has enabled\n\t// IPv6 in /etc/docker/daemon.json.\n\tcn := info.Name\n\tcli, err := docker.GetClient(ctx)\n\tif err != nil {\n\t\treturn errcat.NoDaemonLogs.New(err)\n\t}\n\n\tteleroutePlugin := docker.NetworkPluginName(ctx)\n\tteleroutePort := client.GetConfig(ctx).Grpc().TeleroutePort\n\terr = teleroute.CreateNetwork(ctx, info, cli, teleroutePlugin, teleroutePort)\n\tif err != nil && strings.Contains(err.Error(), fmt.Sprintf(\"%s already exists\", cn)) && teleroute.IsTelerouteNetwork(ctx, cli, cn) {\n\t\tvar disconnected []string\n\t\tdisconnected, err = teleroute.RemoveNetwork(ctx, cli, cn)\n\t\tif err == nil {\n\t\t\terr = teleroute.CreateNetwork(ctx, info, cli, teleroutePlugin, teleroutePort)\n\t\t\tif err == nil {\n\t\t\t\tteleroute.ReconnectNetwork(ctx, cli, cn, disconnected)\n\t\t\t}\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn errcat.NoDaemonLogs.Newf(\"Unable to create network %s: %v\", cn, err)\n\t}\n\terr = teleroute.NetworkGC(ctx, cli)\n\tif err != nil {\n\t\treturn errcat.NoDaemonLogs.Newf(\"Unable to garbage collect teleroute networks: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc maybeDeleteNetwork(ctx context.Context, info *daemon.Info) {\n\t// Wait for container exit.\n\tdc, err := docker.GetClient(ctx)\n\tif err == nil {\n\t\terr = docker.WaitForExit(ctx, dc, info.ContainerID, 3*time.Second)\n\t\tif err == nil {\n\t\t\terr = teleroute.NetworkGC(ctx, dc)\n\t\t}\n\t}\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/connect/daemon.go",
    "content": "package connect\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nfunc launchDaemon(ctx context.Context, cr *daemon.Request) (info *daemon.RootInfo, err error) {\n\tlogFile := filepath.Join(filelocation.AppUserLogDir(ctx), \"daemon.log\")\n\tlogFile, err = logging.ValidateLogFilePath(logFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Ensure that the logfile is present before the daemon starts so that it isn't created with\n\t// root permissions.\n\tif _, err = os.Stat(logFile); err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn nil, errcat.NoDaemonLogs.New(err)\n\t\t}\n\t\tfh, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY, 0o600)\n\t\tif err != nil {\n\t\t\treturn nil, errcat.NoDaemonLogs.New(err)\n\t\t}\n\t\t_ = fh.Close()\n\t}\n\tcacheDir := filelocation.AppUserCacheDir(ctx)\n\tdaemonDir := filepath.Join(cacheDir, \"rootd\")\n\t_ = os.MkdirAll(daemonDir, 0o777)\n\n\tfp, err := ioutil.FreePortsTCP(1)\n\tif err != nil {\n\t\treturn nil, errcat.NoDaemonLogs.New(err)\n\t}\n\tinfo = &daemon.RootInfo{DaemonPort: fp[0].Port()}\n\terr = daemon.NewRootInfoLoader(ctx, false).SaveInfo(info, daemon.InfoFileName)\n\tif err != nil {\n\t\treturn nil, errcat.NoDaemonLogs.New(err)\n\t}\n\n\taddr := fmt.Sprintf(\":%d\", info.DaemonPort)\n\targs := []string{client.GetExe(ctx), client.RootDaemonName, \"--cache\", cacheDir, \"--config\", client.GetConfigFile(ctx), \"--logfile\", logFile, \"--address\", addr}\n\tif cr != nil && cr.RootDaemonProfilingPort > 0 {\n\t\targs = append(args, \"--pprof\", strconv.Itoa(int(cr.RootDaemonProfilingPort)))\n\t}\n\treturn info, proc.StartInBackgroundAsRoot(ctx, args...)\n}\n\n// EnsureRootDaemonRunning ensures that the daemon is running.\nfunc EnsureRootDaemonRunning(ctx context.Context) error {\n\tcr := daemon.GetRequest(ctx)\n\tif cr != nil && cr.Docker {\n\t\t// Never start root daemon when connecting using a docker container.\n\t\treturn nil\n\t}\n\tif addr := client.GetEnv(ctx).UserDaemonAddress; addr != \"\" {\n\t\t// Always assume that root daemon is running when a user daemon address is provided\n\t\treturn nil\n\t}\n\n\t_, err := daemon.LoadRootServiceInfo(ctx)\n\tif err == nil {\n\t\t// Root daemon is running as a managed service.\n\t\treturn nil\n\t}\n\n\til := daemon.NewRootInfoLoader(ctx, false)\n\t_, err = il.LoadInfo(daemon.InfoFileName)\n\tif err != nil {\n\t\t_, err = launchDaemon(ctx, cr)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to launch the daemon service: %w\", err)\n\t\t}\n\t}\n\n\t// Wait for the root daemon to be ready\n\tvar conn *grpc.ClientConn\n\tconn, err = il.DialDaemon(ctx, true)\n\tif err == nil {\n\t\t_ = conn.Close()\n\t}\n\treturn err\n}\n\nfunc mkdir(dirType, path string) error {\n\tif err := os.MkdirAll(path, 0o700); err != nil {\n\t\treturn errcat.NoDaemonLogs.Errorf(err, \"unable to ensure that %s directory %q exists\", dirType, path)\n\t}\n\treturn nil\n}\n\nfunc ensureAppUserCacheDirs(ctx context.Context) error {\n\tcacheDir := filelocation.AppUserCacheDir(ctx)\n\tif err := mkdir(\"cache\", filepath.Join(cacheDir, \"daemons\")); err != nil {\n\t\treturn err\n\t}\n\tif err := mkdir(\"cache\", filepath.Join(cacheDir, \"sessions\")); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc ensureAppUserConfigDir(ctx context.Context) error {\n\tconfigDir := filelocation.AppUserConfigDir(ctx)\n\terr := mkdir(\"config\", configDir)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = client.InstallID(ctx)\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/cli/connect/init_command.go",
    "content": "package connect\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ann\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n)\n\nfunc InitProgressWriter(cmd *cobra.Command) {\n\tctx := cmd.Context()\n\tmode := progress.ModeAuto\n\tif output.WantsFormatted(cmd) {\n\t\tmode = progress.ModeQuiet\n\t} else if progress.IsNoOp(ctx) {\n\t\tif pf := cmd.Flag(global.FlagProgress); pf != nil && pf.Changed {\n\t\t\tmode = progress.Mode(pf.Value.String())\n\t\t} else if me, ok := dos.LookupEnv(ctx, \"TELEPRESENCE_PROGRESS\"); ok {\n\t\t\tmode = progress.Mode(me)\n\t\t} else if pa, ok := cmd.Annotations[ann.Progress]; ok {\n\t\t\tmode = progress.Mode(pa)\n\t\t}\n\t}\n\tw := progress.NewWriter(dos.Stdout(ctx), dos.Stderr(ctx), mode)\n\tcmd.SetContext(progress.WithContextWriter(ctx, w))\n}\n\nfunc InitCommand(cmd *cobra.Command) (err error) {\n\tInitProgressWriter(cmd)\n\tctx := cmd.Context()\n\tas := cmd.Annotations\n\n\tif v, ok := as[ann.Session]; ok {\n\t\tas[ann.UserDaemon] = v\n\t\tas[ann.VersionCheck] = ann.Required\n\t}\n\tprogressStarted := false\n\tdefer func() {\n\t\tif progressStarted {\n\t\t\tprogress.Stop(ctx)\n\t\t}\n\t}()\n\n\tif v := as[ann.UserDaemon]; v == ann.Optional || v == ann.Required {\n\t\tif cr := daemon.GetRequest(ctx); cr == nil {\n\t\t\tif ctx, err = daemon.WithDefaultRequest(cmd); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tflags.DeprecationIfChanged(cmd, global.FlagDocker, \"use telepresence connect to initiate the connection\")\n\t\t\tflags.DeprecationIfChanged(cmd, global.FlagContext, \"use telepresence connect to initiate the connection\")\n\t\t}\n\t\tctx, err = EnsureUserDaemon(ctx, v == ann.Required)\n\t\tif err != nil {\n\t\t\tif v == ann.Optional && (errors.Is(err, daemon.ErrNoUserDaemon) || errcat.GetCategory(err) == errcat.Config) {\n\t\t\t\t// This is OK, but further initialization is not possible\n\t\t\t\terr = nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t\tcmd.SetContext(ctx)\n\t} else {\n\t\t// The rest requires a user daemon\n\t\treturn nil\n\t}\n\tif as[ann.VersionCheck] == ann.Required {\n\t\tif err = ensureDaemonVersion(ctx); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif v := as[ann.Session]; v == ann.Optional || v == ann.Required {\n\t\tprogress.Start(ctx, \"Connecting\")\n\t\tprogressStarted = true\n\t\tctx, err = EnsureSession(ctx, cmd.UseLine(), v == ann.Required)\n\t\tdefer progress.Stop(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcmd.SetContext(ctx)\n\t}\n\treturn nil\n}\n\nfunc GetOptionalSession(cmd *cobra.Command) (context.Context, *daemon.Session, error) {\n\tcmd.Annotations[ann.Session] = ann.Optional\n\terr := InitCommand(cmd)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tctx := cmd.Context()\n\treturn ctx, daemon.GetSession(ctx), nil\n}\n"
  },
  {
    "path": "pkg/client/cli/connect/version_check.go",
    "content": "package connect\n\nimport (\n\t\"context\"\n\t\"regexp\"\n\t\"strconv\"\n\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nvar validPrerelRx = regexp.MustCompile(`^[a-z]+\\.\\d+$`)\n\nfunc versionCheck(ctx context.Context, daemonBinary string) error {\n\tif debug, err := strconv.ParseBool(dos.Getenv(ctx, \"TELEPRESENCE_DEBUG\")); err == nil && debug {\n\t\treturn nil\n\t}\n\n\t// Ensure that the already running daemons have the correct version\n\tuserD := daemon.MustGetUserClient(ctx)\n\tuv := userD.Semver()\n\tif userD.Containerized() {\n\t\t// The user-daemon is remote (in a docker container, most likely). Compare the major, minor, and patch. Only\n\t\t// compare pre-release if it's rc.X or test.X, and don't check if the binaries match.\n\t\tcv := version.Structured\n\t\tif cv.Major == uv.Major && cv.Minor == uv.Minor && cv.Patch == uv.Patch {\n\t\t\tif len(cv.Pre) != 1 {\n\t\t\t\t// Prerelease does not consist of exactly one element, so it either doesn't exist or we don't care about it.\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif pv := cv.Pre[0].VersionStr; !validPrerelRx.MatchString(pv) || len(uv.Pre) == 1 && pv == uv.Pre[0].VersionStr {\n\t\t\t\t// Either not a prerelease that we care about comparing, or the prerelease was exactly equal.\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn errcat.User.Newf(\"version mismatch. Client %s != remote user daemon %s\", version.Version, uv)\n\t}\n\tif !version.Structured.EQ(uv) {\n\t\t// OSS Version mismatch. We never allow this\n\t\treturn errcat.User.Newf(\"version mismatch. Client %s != user daemon %s, please run 'telepresence quit -s' and reconnect\",\n\t\t\tversion.Version, uv)\n\t}\n\tif daemonBinary != \"\" && userD.Executable() != daemonBinary {\n\t\treturn errcat.User.Newf(\"executable mismatch. Connector using %s, configured to use %s, please run 'telepresence quit -s' and reconnect\",\n\t\t\tuserD.Executable(), daemonBinary)\n\t}\n\tvr, err := userD.RootDaemonVersion(ctx, &empty.Empty{})\n\tif err != nil {\n\t\treturn tpGrpc.FromGRPC(err)\n\t}\n\tif version.Version != vr.Version {\n\t\treturn errcat.User.Newf(\"version mismatch. Client %s != Root Daemon %s, please run 'telepresence quit -s' and reconnect\",\n\t\t\tversion.Version, vr.Version)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/daemon/dial.go",
    "content": "package daemon\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"google.golang.org/grpc\"\n)\n\nconst InfoFileName = \"daemon.json\"\n\nvar ErrNoRootDaemon = errors.New(\"telepresence root daemon is not running\")\n\nvar ErrNoUserDaemon = errors.New(\"telepresence user daemon is not running\")\n\nfunc DialRootDaemon(ctx context.Context, waitForConnect bool) (conn *grpc.ClientConn, err error) {\n\tif ri, err := LoadRootServiceInfo(ctx); err == nil {\n\t\treturn dialDaemon(ctx, \"root\", ri.DaemonPort)\n\t}\n\treturn NewRootInfoLoader(ctx, false).DialDaemon(ctx, waitForConnect)\n}\n\nfunc DialUserDaemon(ctx context.Context, waitForConnect bool) (conn *grpc.ClientConn, err error) {\n\treturn NewUserInfoLoader(ctx).DialDaemon(ctx, waitForConnect)\n}\n"
  },
  {
    "path": "pkg/client/cli/daemon/identifier.go",
    "content": "package daemon\n\nimport (\n\t\"context\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype Identifier struct {\n\tName          string\n\tKubeContext   string\n\tNamespace     string\n\tContainerized bool\n}\n\nfunc NewIdentifier(name, contextName, namespace string, containerized bool) *Identifier {\n\tif namespace == \"\" {\n\t\tnamespace = \"default\"\n\t}\n\tif name == \"\" {\n\t\tif contextName == \"\" {\n\t\t\t// Must be an in-cluster config\n\t\t\tname = \"in-cluster-\" + namespace\n\t\t} else {\n\t\t\tname = contextName + \"-\" + namespace\n\t\t}\n\t\tif containerized {\n\t\t\tname += \"-cn\"\n\t\t}\n\t}\n\treturn &Identifier{\n\t\tKubeContext:   contextName,\n\t\tNamespace:     namespace,\n\t\tName:          ioutil.SafeName(name),\n\t\tContainerized: containerized,\n\t}\n}\n\nfunc (id *Identifier) String() string {\n\treturn id.Name\n}\n\nfunc (id *Identifier) InfoFileName() string {\n\tif id.Containerized && id.Name != \"\" {\n\t\treturn id.Name + \".json\"\n\t}\n\treturn InfoFileName\n}\n\nfunc (id *Identifier) ContainerName() string {\n\treturn id.String()\n}\n\n// IdentifierFromFlags returns a unique name created from the name of the current context\n// and the active namespace denoted by the given flagMap.\nfunc IdentifierFromFlags(ctx context.Context, name string, flagMap map[string]string, kubeConfigData []byte, containerized bool) (*Identifier, error) {\n\tcc := flagMap[\"context\"]\n\tns := flagMap[\"namespace\"]\n\tif cc == \"\" || ns == \"\" {\n\t\tcld, err := k8s.ConfigLoader(ctx, flagMap, kubeConfigData)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif ns == \"\" {\n\t\t\tns, _, err = cld.Namespace()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif cc == \"\" {\n\t\t\tconfig, err := cld.RawConfig()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcc = config.CurrentContext\n\t\t}\n\t}\n\treturn NewIdentifier(name, cc, ns, containerized), nil\n}\n"
  },
  {
    "path": "pkg/client/cli/daemon/identifier_test.go",
    "content": "package daemon_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\nfunc TestDaemonInfoFileName(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tnamespace   string\n\t\tresult      string\n\t\tinContainer bool\n\t}{\n\t\t{name: \"the-cure\", namespace: \"ns1\", result: \"the-cure-ns1-cn.json\", inContainer: true},\n\t\t{name: \"arn:aws:eks:us-east-2:914373874199:cluster/test-auth\", namespace: \"ns1\", result: \"arn_aws_eks_us-east-2_914373874199_cluster_test-auth-ns1-cn.json\", inContainer: true},\n\t\t{name: \"gke_datawireio_us-central1-b_kube-staging-apps-1\", namespace: \"ns1\", result: \"gke_datawireio_us-central1-b_kube-staging-apps-1-ns1-cn.json\", inContainer: true},\n\t\t{name: \"the-cure\", namespace: \"ns1\", result: \"daemon.json\", inContainer: false},\n\t}\n\tfor _, test := range tests {\n\t\tdi := daemon.NewIdentifier(\"\", test.name, test.namespace, test.inContainer)\n\t\tresult := di.InfoFileName()\n\t\tif result != test.result {\n\t\t\tt.Fatalf(\"DaemonInfoFile gave bad output; expected %s got %s\", test.result, result)\n\t\t}\n\t}\n}\n\nfunc TestSafeContainerName(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\twant string\n\t}{\n\t\t{\n\t\t\t\"@\",\n\t\t\t\"a\",\n\t\t},\n\t\t{\n\t\t\t\"@x\",\n\t\t\t\"ax\",\n\t\t},\n\t\t{\n\t\t\t\"x@\",\n\t\t\t\"x_\",\n\t\t},\n\t\t{\n\t\t\t\"x@y\",\n\t\t\t\"x_y\",\n\t\t},\n\t\t{\n\t\t\t\"x™y\", // multibyte char\n\t\t\t\"x_y\",\n\t\t},\n\t\t{\n\t\t\t\"x™\", // multibyte char\n\t\t\t\"x_\",\n\t\t},\n\t\t{\n\t\t\t\"_y\",\n\t\t\t\"ay\",\n\t\t},\n\t\t{\n\t\t\t\"_y_\",\n\t\t\t\"ay_\",\n\t\t},\n\t\t// TODO: Add test cases.\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := ioutil.SafeName(tt.name); got != tt.want {\n\t\t\t\tt.Errorf(\"SafeName() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/daemon/info.go",
    "content": "package daemon\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/go-json-experiment/json\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cache\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\tgrpcClient \"github.com/telepresenceio/telepresence/v2/pkg/grpc/client\"\n)\n\ntype RootInfo struct {\n\tDaemonPort uint16 `json:\"daemon_port,omitempty\"`\n}\n\ntype Info struct {\n\tName         string     `json:\"name,omitempty\"`\n\tKubeContext  string     `json:\"kube_context,omitempty\"`\n\tNamespace    string     `json:\"namespace,omitempty\"`\n\tDaemonPort   uint16     `json:\"daemon_port,omitempty\"`\n\tExposedPorts []string   `json:\"exposed_ports,omitempty\"`\n\tHostname     string     `json:\"hostname,omitempty\"`\n\tContainerPID int        `json:\"container_pid,omitempty\"`\n\tContainerIP  netip.Addr `json:\"container_ip,omitempty\"`\n\tContainerID  string     `json:\"container_id,omitempty\"`\n\tComposeFile  string     `json:\"compose_file,omitempty\"`\n}\n\ntype TCPInfo interface {\n\tRootInfo | Info\n}\n\nfunc (info *Info) DaemonID() *Identifier {\n\treturn NewIdentifier(info.Name, info.KubeContext, info.Namespace, info.InDocker())\n}\n\nfunc (info *Info) InDocker() bool {\n\treturn info.ContainerPID != 0\n}\n\nfunc (info *Info) SetConnectionInfo(name string, clusterContext string, namespace string) {\n\tinfo.Name = name\n\tinfo.KubeContext = clusterContext\n\tinfo.Namespace = namespace\n}\n\nconst (\n\tdaemonsDirName     = \"userd\"\n\trootDaemonsDirName = \"rootd\"\n\tkeepAliveInterval  = 2 * time.Second\n\tmaxNoSignOfLife    = 3 * keepAliveInterval\n)\n\ntype InfoLoader[T TCPInfo] struct {\n\tctx     context.Context\n\tdirName string\n}\n\nfunc NewUserInfoLoader(ctx context.Context) *InfoLoader[Info] {\n\treturn &InfoLoader[Info]{ctx: ctx, dirName: daemonsDirName}\n}\n\nfunc NewRootInfoLoader(ctx context.Context, managed bool) *InfoLoader[RootInfo] {\n\tif managed {\n\t\t// The root daemon is running as a service, so use the root cache dir\n\t\tctx = filelocation.WithAppUserCacheDir(ctx, filelocation.RootCacheDir)\n\t}\n\treturn &InfoLoader[RootInfo]{ctx: ctx, dirName: rootDaemonsDirName}\n}\n\nfunc LoadRootServiceInfo(ctx context.Context) (*RootInfo, error) {\n\treturn NewRootInfoLoader(ctx, true).LoadInfo(InfoFileName)\n}\n\nfunc (il *InfoLoader[T]) LoadInfo(file string) (*T, error) {\n\tpath := filepath.Join(filelocation.AppUserCacheDir(il.ctx), il.dirName, file)\n\tf, err := dos.OpenFile(dos.WithLockedFs(il.ctx), path, os.O_RDONLY, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\tfi, err := f.Stat()\n\tif err == nil {\n\t\terr = il.deleteIfStale(file, fi)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tjsonContent, err := io.ReadAll(f)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar di T\n\tif err := json.Unmarshal(jsonContent, &di); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse JSON from file %s: %w\", path, err)\n\t}\n\treturn &di, nil\n}\n\nfunc (il *InfoLoader[T]) SaveInfo(object *T, file string) error {\n\treturn cache.SaveToUserCache(il.ctx, object, filepath.Join(il.dirName, file), cache.Public)\n}\n\nfunc (il *InfoLoader[T]) DeleteInfo(file string) error {\n\treturn cache.DeleteFromUserCache(il.ctx, filepath.Join(il.dirName, file))\n}\n\nfunc (il *InfoLoader[T]) InfoExists(file string) (bool, error) {\n\tst, err := dos.Stat(dos.WithLockedFs(il.ctx), filepath.Join(filelocation.AppUserCacheDir(il.ctx), il.dirName, file))\n\tif err == nil {\n\t\terr = il.deleteIfStale(file, st)\n\t}\n\tif err != nil {\n\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\terr = nil\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\nfunc (il *InfoLoader[T]) WatchInfos(onChange func(context.Context) error, files ...string) error {\n\treturn cache.WatchUserCache(il.ctx, il.dirName, onChange, files...)\n}\n\nfunc (il *InfoLoader[T]) WaitUntilAllVanishes(ttw time.Duration) error {\n\tgiveUp := time.Now().Add(ttw)\n\tfor giveUp.After(time.Now()) {\n\t\tfiles, err := il.infoFiles()\n\t\tif err != nil || len(files) == 0 {\n\t\t\treturn err\n\t\t}\n\t\ttime.Sleep(250 * time.Millisecond)\n\t}\n\treturn errors.New(\"timeout while waiting for daemon files to vanish\")\n}\n\nfunc (il *InfoLoader[T]) DeleteAllInfos() error {\n\tfiles, err := il.infoFiles()\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, file := range files {\n\t\t_ = cache.DeleteFromUserCache(il.ctx, filepath.Join(il.dirName, file.Name()))\n\t}\n\treturn nil\n}\n\nfunc (il *InfoLoader[T]) LoadInfos() ([]*T, error) {\n\tfiles, err := il.infoFiles()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tDaemonInfos := make([]*T, len(files))\n\tfor i, file := range files {\n\t\tif err = cache.LoadFromUserCache(il.ctx, &DaemonInfos[i], filepath.Join(il.dirName, file.Name())); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn DaemonInfos, nil\n}\n\nfunc (il *InfoLoader[T]) DialDaemon(ctx context.Context, waitForConnect bool) (conn *grpc.ClientConn, err error) {\n\tvar info *T\n\tvar cancel context.CancelFunc\n\tif waitForConnect {\n\t\tctx, cancel = context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer cancel()\n\t\terr = backoff.Retry(func() error {\n\t\t\tinfo, err = il.LoadInfo(InfoFileName)\n\t\t\treturn err\n\t\t}, backoff.WithContext(backoff.NewConstantBackOff(200*time.Millisecond), ctx))\n\t} else {\n\t\tctx, cancel = context.WithTimeout(ctx, 500*time.Millisecond)\n\t\tinfo, err = il.LoadInfo(InfoFileName)\n\t}\n\tdefer cancel()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar daemonName string\n\tvar daemonPort uint16\n\tif ii, ok := any(info).(*Info); ok {\n\t\tdaemonName = \"user\"\n\t\tdaemonPort = ii.DaemonPort\n\t} else {\n\t\tdaemonName = \"root\"\n\t\tdaemonPort = (any(info).(*RootInfo)).DaemonPort\n\t}\n\tconn, err = dialDaemon(ctx, daemonName, daemonPort)\n\tif errors.Is(err, context.DeadlineExceeded) && !waitForConnect {\n\t\t// A race may occur where the daemon is shutting down. We found the info file, but the daemon has since stopped responding.\n\t\tif daemonName == \"user\" {\n\t\t\terr = ErrNoUserDaemon\n\t\t} else {\n\t\t\terr = ErrNoRootDaemon\n\t\t}\n\t}\n\treturn conn, err\n}\n\nfunc dialDaemon(ctx context.Context, name string, port uint16) (conn *grpc.ClientConn, err error) {\n\tconn, err = grpcClient.DialGRPC(ctx, fmt.Sprintf(\"127.0.0.1:%d\", port),\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\tgrpc.WithNoProxy())\n\tif err != nil {\n\t\terr = fmt.Errorf(\"unable to dial %s daemon port %d: %w\", name, port, err)\n\t}\n\treturn conn, err\n}\n\nfunc (il *InfoLoader[T]) infoFiles() ([]fs.DirEntry, error) {\n\tfiles, err := os.ReadDir(filepath.Join(filelocation.AppUserCacheDir(il.ctx), il.dirName))\n\tif err != nil {\n\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\terr = nil\n\t\t}\n\t\treturn nil, err\n\t}\n\tactive := make([]fs.DirEntry, 0, len(files))\n\tfor _, file := range files {\n\t\tfi, err := file.Info()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = il.deleteIfStale(file.Name(), fi)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, os.ErrNotExist) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tactive = append(active, file)\n\t}\n\treturn active, err\n}\n\nfunc (il *InfoLoader[T]) deleteIfStale(name string, fi fs.FileInfo) error {\n\tage := time.Since(fi.ModTime())\n\tif age > maxNoSignOfLife {\n\t\tname = filepath.Join(il.dirName, name)\n\t\tclog.Debugf(il.ctx, \"Deleting stale info %s with age = %s\", name, age)\n\t\tif err := cache.DeleteFromUserCache(il.ctx, name); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn fmt.Errorf(\"%s: %w (file stale and removed)\", name, fs.ErrNotExist)\n\t}\n\treturn nil\n}\n\nfunc infoName[T TCPInfo](info *T) string {\n\tif ii, ok := any(info).(*Info); ok {\n\t\treturn ii.DaemonID().String()\n\t}\n\treturn \"rootd\"\n}\n\ntype InfoMatchError string\n\nfunc (i InfoMatchError) Error() string {\n\treturn string(i)\n}\n\ntype MultipleDaemonsError []*Info //nolint:errname // Don't want a plural name just because the type is a slice\n\nfunc (m MultipleDaemonsError) Error() string {\n\tsb := strings.Builder{}\n\tsb.WriteString(\"multiple daemons are running, please select \")\n\tl := len(m)\n\ti := 0\n\tif l > 2 {\n\t\tsb.WriteString(\"one of \")\n\t\tfor ; i+2 < l; i++ {\n\t\t\tsb.WriteString(m[i].DaemonID().Name)\n\t\t\tsb.WriteString(\", \")\n\t\t}\n\t} else {\n\t\tsb.WriteString(m[i].DaemonID().Name)\n\t\ti++\n\t}\n\tsb.WriteString(\" or \")\n\tsb.WriteString(m[i].DaemonID().Name)\n\tsb.WriteString(\" using the --use <match> flag\")\n\treturn sb.String()\n}\n\n// LoadMatchingInfo loads the daemon info matching the given regexp or returns an error if there is none or more than one.\nfunc (il *InfoLoader[T]) LoadMatchingInfo(match *regexp.Regexp) (*T, error) {\n\tinfos, err := il.LoadInfos()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif match != nil {\n\t\tinfos = slices.DeleteFunc(infos, func(i *T) bool {\n\t\t\treturn !match.MatchString(infoName(i))\n\t\t})\n\t}\n\tswitch len(infos) {\n\tcase 0:\n\t\tif match == nil {\n\t\t\terr = fmt.Errorf(\"unable to find daemon info matching %s: %w\", match, os.ErrNotExist)\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"unable to find daemon info: %w\", os.ErrNotExist)\n\t\t}\n\t\treturn nil, err\n\tcase 1:\n\t\treturn infos[0], nil\n\tdefault:\n\t\tif iis, ok := any(infos).([]*Info); ok {\n\t\t\treturn nil, MultipleDaemonsError(iis)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"unexpectedly found multiple %T infos\", infos[0])\n\t}\n}\n\n// CancelWhenRmFromCache watches for the file to be removed from the cache, then calls cancel.\nfunc (il *InfoLoader[T]) CancelWhenRmFromCache(cancel context.CancelFunc, filename string) error {\n\treturn il.WatchInfos(func(ctx context.Context) error {\n\t\texists, err := il.InfoExists(filename)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !exists {\n\t\t\t// spec removed from cache, shut down gracefully\n\t\t\tclog.Infof(ctx, \"daemon file %s removed from cache, shutting down gracefully\", filename)\n\t\t\tcancel()\n\t\t}\n\t\treturn nil\n\t}, filename)\n}\n\n// KeepInfoAlive updates the access and modification times of the given file\n// periodically so that it never gets older than keepAliveInterval. This means that\n// any file with a modification time older than the current time minus three keepAliveIntervals\n// can be considered stale and should be removed.\n//\n// The alive-poll ends, and the file is deleted when the context is canceled.\nfunc (il *InfoLoader[T]) KeepInfoAlive(file string) error {\n\tdaemonFile := filepath.Join(filelocation.AppUserCacheDir(il.ctx), il.dirName, file)\n\tticker := time.NewTicker(keepAliveInterval)\n\tdefer ticker.Stop()\n\tnow := time.Now()\n\tfor {\n\t\tif err := os.Chtimes(daemonFile, now, now); err != nil {\n\t\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\t\t// File is removed, so stop trying to update its timestamps\n\t\t\t\tclog.Debugf(il.ctx, \"Daemon info %s does not exist\", daemonFile)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"failed to update timestamp on %s: %w\", daemonFile, err)\n\t\t}\n\t\tselect {\n\t\tcase <-il.ctx.Done():\n\t\t\tclog.Debugf(il.ctx, \"Deleting daemon info %s because context was cancelled\", file)\n\t\t\t_ = il.DeleteInfo(file)\n\t\t\treturn nil\n\t\tcase now = <-ticker.C:\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/daemon/request.go",
    "content": "package daemon\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\t\"google.golang.org/protobuf/proto\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/slice\"\n)\n\ntype Request struct {\n\t*connector.ConnectRequest\n\n\t// If set, then use a containerized daemon for the connection.\n\tDocker bool\n\n\t// Ports exposed by a containerized daemon. Only valid when Docker == true\n\tExposedPorts []string\n\n\t// Hostname used by a containerized daemon. Only valid when Docker == true\n\tHostname string\n\n\t// Match expression to use when finding an existing connection by name\n\tUse *regexp.Regexp\n\n\t// Request is created on-demand, not by InitRequest\n\tImplicit bool\n\n\tkubeConfig              *genericclioptions.ConfigFlags\n\tUserDaemonProfilingPort uint16\n\tRootDaemonProfilingPort uint16\n\n\t// proxyVia holds the string version for the --proxy-via flag values.\n\tproxyVia []string\n\n\t// vnats holds the string version for the --vnat flag values.\n\tvnats []string\n\n\t// LocalReroutes maps ports on localhost to remote ports.\n\tLocalReroutes []string\n\n\t// RemoteReroutes uses the VIF to reroute remote host ports.\n\tRemoteReroutes []string\n\n\t// Aliases by which this daemon can be referenced (added to the telepresence network).\n\tNetworkAliases []string\n}\n\ntype CobraRequest struct {\n\tRequest\n\tkubeFlagSet *pflag.FlagSet\n}\n\n// InitRequest adds the networking flags and Kubernetes flags to the given command and\n// returns a Request and a FlagSet with the Kubernetes flags. The FlagSet is returned\n// here so that a map of flags that gets modified can be extracted using FlagMap once the flag\n// parsing has completed.\nfunc InitRequest(cmd *cobra.Command) *CobraRequest {\n\tcr := CobraRequest{\n\t\tRequest: Request{\n\t\t\tConnectRequest: &connector.ConnectRequest{},\n\t\t},\n\t}\n\tflags := cmd.Flags()\n\n\tnwFlags := pflag.NewFlagSet(\"Telepresence networking flags\", 0)\n\tnwFlags.StringVar(&cr.Name, \"name\", \"\", \"Optional name to use for the connection\")\n\tnwFlags.StringSliceVar(&cr.MappedNamespaces,\n\t\t\"mapped-namespaces\", nil, ``+\n\t\t\t`Comma separated list of namespaces considered by DNS resolver and NAT for outbound connections. `+\n\t\t\t`Defaults to all namespaces`)\n\tnwFlags.StringVar(&cr.ManagerNamespace, \"manager-namespace\", \"\", `The namespace where the traffic manager is to be found. `+\n\t\t`Overrides any other manager namespace set in config`)\n\tnwFlags.StringSliceVar(&cr.AlsoProxy,\n\t\t\"also-proxy\", nil, ``+\n\t\t\t`Additional comma separated list of CIDR to proxy`)\n\tnwFlags.StringSliceVar(&cr.NeverProxy,\n\t\t\"never-proxy\", nil, ``+\n\t\t\t`Comma separated list of CIDR to never proxy`)\n\tnwFlags.StringSliceVar(&cr.vnats,\n\t\t\"vnat\", nil, ``+\n\t\t\t`Use Network Address Translation to create virtual IPs for the given CIDR. CIDR can be substituted for the `+\n\t\t\t`symblic name \"service\", \"pods\", \"also\", or \"all\".`)\n\tnwFlags.StringSliceVar(&cr.LocalReroutes,\n\t\t\"reroute-local\", nil, ``+\n\t\t\t`Reroute port on local host to remote host. Format is <local port>:<host>:<port>[/{tcp,udp}]. `+\n\t\t\t`<port> can be symbolic when <host> is a service name.`)\n\tnwFlags.StringSliceVar(&cr.RemoteReroutes,\n\t\t\"reroute-remote\", nil, ``+\n\t\t\t`Reroute port on remote host. Format is <host>:<port>:<new port>[/{tcp,udp}]. `+\n\t\t\t`<port> can be symbolic when <host> is a service name.`)\n\tnwFlags.StringSliceVar(&cr.proxyVia,\n\t\t\"proxy-via\", nil, ``+\n\t\t\t`Use Network Address Translation to create virtual IPs for the given CIDR, and route via WORKLOAD. Must be in the `+\n\t\t\t`form CIDR=WORKLOAD. CIDR can be substituted for the symblic name \"service\", \"pods\", \"also\", or \"all\".`)\n\tnwFlags.StringSliceVar(&cr.AllowConflictingSubnets,\n\t\t\"allow-conflicting-subnets\", nil, ``+\n\t\t\t`Comma separated list of CIDR that will be allowed to conflict with local subnets`)\n\n\t// Docker flags\n\tnwFlags.Bool(global.FlagDocker, false, \"Start, or connect to, daemon in a docker container\")\n\tnwFlags.StringArrayVar(&cr.ExposedPorts,\n\t\t\"expose\", nil, ``+\n\t\t\t`Port that a containerized daemon will expose. See docker run -p for more info. Can be repeated`)\n\tnwFlags.StringVar(&cr.Hostname,\n\t\t\"hostname\", \"\", ``+\n\t\t\t`Hostname used by a containerized daemon`)\n\n\tflags.AddFlagSet(nwFlags)\n\n\tdbgFlags := pflag.NewFlagSet(\"Debug and Profiling flags\", 0)\n\tdbgFlags.Uint16Var(&cr.UserDaemonProfilingPort,\n\t\t\"userd-profiling-port\", 0, \"Start a pprof server in the user daemon on this port\")\n\t_ = dbgFlags.MarkHidden(\"userd-profiling-port\")\n\tdbgFlags.Uint16Var(&cr.RootDaemonProfilingPort,\n\t\t\"rootd-profiling-port\", 0, \"Start a pprof server in the root daemon on this port\")\n\t_ = dbgFlags.MarkHidden(\"rootd-profiling-port\")\n\tflags.AddFlagSet(dbgFlags)\n\n\tcr.kubeConfig = genericclioptions.NewConfigFlags(false)\n\tcr.KubeFlags = make(map[string]string)\n\tcr.kubeFlagSet = pflag.NewFlagSet(\"Kubernetes flags\", 0)\n\tcr.kubeConfig.AddFlags(cr.kubeFlagSet)\n\tflags.AddFlagSet(cr.kubeFlagSet)\n\t_ = cmd.RegisterFlagCompletionFunc(\"mapped-namespaces\", cr.autocompleteNamespaces)\n\t_ = cmd.RegisterFlagCompletionFunc(\"manager-namespace\", cr.autocompleteNamespace)\n\t_ = cmd.RegisterFlagCompletionFunc(\"namespace\", cr.autocompleteNamespace)\n\t_ = cmd.RegisterFlagCompletionFunc(\"cluster\", cr.autocompleteCluster)\n\treturn &cr\n}\n\ntype requestKey struct{}\n\nfunc (cr *CobraRequest) CommitFlags(cmd *cobra.Command) error {\n\tvar err error\n\tcr.kubeFlagSet.VisitAll(func(flag *pflag.Flag) {\n\t\tif flag.Changed {\n\t\t\tvar v string\n\t\t\tif sv, ok := flag.Value.(pflag.SliceValue); ok {\n\t\t\t\tv = slice.AsCSV(sv.GetSlice())\n\t\t\t} else {\n\t\t\t\tv = flag.Value.String()\n\t\t\t\tif flag.Name == \"kubeconfig\" && v == \"-\" {\n\t\t\t\t\t// Read kubeconfig from stdin\n\t\t\t\t\tcr.KubeconfigData, err = io.ReadAll(cmd.InOrStdin())\n\t\t\t\t\treturn // kubernetes will not understand \"-\"\n\t\t\t\t}\n\t\t\t}\n\t\t\tcr.KubeFlags[flag.Name] = v\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// A --vnat CIDR is the same as --proxy-via CIDR=local\n\tfor _, vnat := range cr.vnats {\n\t\tcr.proxyVia = append(cr.proxyVia, vnat+\"=local\")\n\t}\n\n\terr = cr.setGlobalConnectFlags(cmd)\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\tctx, err := cr.Commit(cmd.Context())\n\tif err != nil {\n\t\treturn err\n\t}\n\tcmd.SetContext(ctx)\n\treturn nil\n}\n\nfunc (cr *Request) Commit(ctx context.Context) (context.Context, error) {\n\tcr.addKubeconfigEnv()\n\tvar err error\n\tcr.SubnetViaWorkloads, err = parseProxyVias(cr.proxyVia)\n\tif err != nil {\n\t\treturn ctx, errcat.User.New(err)\n\t}\n\tif len(cr.KubeconfigData) > 0 {\n\t\tkc, err := clientcmd.Load(cr.KubeconfigData)\n\t\tif err != nil {\n\t\t\treturn ctx, fmt.Errorf(\"unable to parse kubeconfig: %w\", err)\n\t\t}\n\t\tif cr.KubeFlags == nil {\n\t\t\tcr.KubeFlags = make(map[string]string)\n\t\t}\n\t\tif _, ok := cr.KubeFlags[\"context\"]; !ok {\n\t\t\tcr.KubeFlags[\"context\"] = kc.CurrentContext\n\t\t}\n\t\tif _, ok := cr.KubeFlags[\"namespace\"]; !ok {\n\t\t\tif currCtx, ok := kc.Contexts[kc.CurrentContext]; ok {\n\t\t\t\tcr.KubeFlags[\"namespace\"] = currCtx.Namespace\n\t\t\t}\n\t\t}\n\t\t// kubernetes will not understand \"-\"\n\t\tdelete(cr.KubeFlags, \"kubeconfig\")\n\t}\n\treturn context.WithValue(ctx, requestKey{}, cr), nil\n}\n\ntype prefixViaWL struct {\n\tsubnet   netip.Prefix\n\tsymbolic string\n\tworkload string\n}\n\nfunc parseProxyVias(proxyVia []string) ([]*daemon.SubnetViaWorkload, error) {\n\tl := len(proxyVia)\n\tif l == 0 {\n\t\treturn nil, nil\n\t}\n\tpvs := make([]prefixViaWL, 0, l)\n\tfor _, dps := range proxyVia {\n\t\tdp, err := parseSubnetViaWorkload(dps)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tlastPvs := len(pvs) - 1\n\t\tswitch dp.symbolic {\n\t\tcase \"\":\n\t\t\tfor pi := lastPvs; pi >= 0; pi-- {\n\t\t\t\tpv := pvs[pi]\n\t\t\t\tif pv.symbolic == \"\" && pv.subnet.Overlaps(dp.subnet) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"CIDRs %s and %s are overlapping\", pv.subnet, dp.subnet)\n\t\t\t\t}\n\t\t\t}\n\t\t\tpvs = append(pvs, dp)\n\t\tcase \"all\":\n\t\t\tfor pi := lastPvs; pi >= 0; pi-- {\n\t\t\t\tpv := pvs[pi]\n\t\t\t\tif pv.symbolic != \"\" {\n\t\t\t\t\treturn nil, fmt.Errorf(\"CIDRs %s and %s are overlapping\", pv.symbolic, dp.symbolic)\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Normalize by replacing \"all\" with \"also\", \"pods\", and \"service\"\n\t\t\tfor _, sym := range []string{\"also\", \"pods\", \"service\"} {\n\t\t\t\tpvs = append(pvs,\n\t\t\t\t\tprefixViaWL{\n\t\t\t\t\t\tsymbolic: sym,\n\t\t\t\t\t\tworkload: dp.workload,\n\t\t\t\t\t})\n\t\t\t}\n\t\tdefault:\n\t\t\tfor pi := lastPvs; pi >= 0; pi-- {\n\t\t\t\tpv := pvs[pi]\n\t\t\t\tif pv.symbolic == dp.symbolic {\n\t\t\t\t\treturn nil, fmt.Errorf(\"CIDRs %s and %s are overlapping\", pv.symbolic, dp.symbolic)\n\t\t\t\t}\n\t\t\t}\n\t\t\tpvs = append(pvs, dp)\n\t\t}\n\t}\n\tsvs := make([]*daemon.SubnetViaWorkload, len(pvs))\n\tfor i, pv := range pvs {\n\t\tn := pv.symbolic\n\t\tif n == \"\" {\n\t\t\tn = pv.subnet.String()\n\t\t}\n\t\tsvs[i] = &daemon.SubnetViaWorkload{\n\t\t\tSubnet:   n,\n\t\t\tWorkload: pv.workload,\n\t\t}\n\t}\n\treturn svs, nil\n}\n\nfunc parseSubnetViaWorkload(dps string) (prefixViaWL, error) {\n\tvar pv prefixViaWL\n\teqIdx := strings.IndexByte(dps, '=')\n\tif eqIdx <= 0 {\n\t\treturn pv, fmt.Errorf(\"--proxy-via %q is not in the format CIDR=WORKLOAD\", dps)\n\t}\n\tlhs := dps[:eqIdx]\n\trhs := dps[eqIdx+1:]\n\tif errs := validation.IsDNS1123Label(rhs); len(errs) > 0 {\n\t\treturn pv, errors.New(errs[0])\n\t}\n\tif sn, err := netip.ParsePrefix(lhs); err != nil {\n\t\tif !(lhs == \"all\" || lhs == \"also\" || lhs == \"pods\" || lhs == \"service\") {\n\t\t\treturn pv, err\n\t\t}\n\t\tpv.symbolic = lhs\n\t} else {\n\t\tpv.subnet = sn\n\t}\n\tpv.workload = rhs\n\treturn pv, nil\n}\n\nfunc (cr *Request) addKubeconfigEnv() {\n\t// Certain options' default are bound to the connector daemon process; this is notably true of the kubeconfig file(s) to use,\n\t// and since those files can be specified, both as a --kubeconfig flag and in the KUBECONFIG setting, and since the flag won't\n\t// accept multiple path entries, we need to pass the environment setting to the connector daemon so that it can set it every\n\t// time it receives a new config.\n\tcr.Environment = make(map[string]string, 2)\n\taddEnv := func(key string) {\n\t\tif v, ok := os.LookupEnv(key); ok {\n\t\t\tcr.Environment[key] = v\n\t\t} else {\n\t\t\t// A dash prefix in the key means \"unset\".\n\t\t\tcr.Environment[\"-\"+key] = \"\"\n\t\t}\n\t}\n\taddEnv(\"KUBECONFIG\")\n\taddEnv(\"GOOGLE_APPLICATION_CREDENTIALS\")\n}\n\n// setContext deals with the global --context flag and assigns it to KubeFlags because it's\n// deliberately excluded from the original flags (to avoid conflict with the global flag).\nfunc (cr *Request) setGlobalConnectFlags(cmd *cobra.Command) error {\n\tif contextFlag := cmd.Flag(global.FlagContext); contextFlag != nil && contextFlag.Changed {\n\t\tcn := contextFlag.Value.String()\n\t\tcr.KubeFlags[global.FlagContext] = cn\n\t\tcr.kubeConfig.Context = &cn\n\t}\n\tif dockerFlag := cmd.Flag(global.FlagDocker); dockerFlag != nil && dockerFlag.Changed {\n\t\tcr.Docker, _ = strconv.ParseBool(dockerFlag.Value.String())\n\t}\n\tif useFlag := cmd.Flag(global.FlagUse); useFlag != nil && useFlag.Changed {\n\t\tvar err error\n\t\tif cr.Use, err = regexp.Compile(useFlag.Value.String()); err != nil {\n\t\t\treturn errcat.User.Newf(\"argument to --use must be a valid regexp: %v\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (cr *Request) Clone() *Request {\n\tcl := *cr\n\tcl.ConnectRequest = proto.Clone(cr.ConnectRequest).(*connector.ConnectRequest)\n\tcl.AllowConflictingSubnets = slices.Clone(cr.AllowConflictingSubnets)\n\tcl.AlsoProxy = slices.Clone(cr.AlsoProxy)\n\tcl.Environment = maps.Copy(cl.Environment)\n\tcl.ExposedPorts = slices.Clone(cr.ExposedPorts)\n\tcl.KubeFlags = maps.Copy(cl.KubeFlags)\n\tcl.MappedNamespaces = slices.Clone(cr.MappedNamespaces)\n\tcl.NeverProxy = slices.Clone(cr.NeverProxy)\n\tcl.SubnetViaWorkloads = slices.Clone(cr.SubnetViaWorkloads)\n\tcl.proxyVia = slices.Clone(cr.proxyVia)\n\treturn &cl\n}\n\nfunc GetRequest(ctx context.Context) *Request {\n\tif cr, ok := ctx.Value(requestKey{}).(*Request); ok {\n\t\treturn cr\n\t}\n\treturn nil\n}\n\nfunc MustGetRequest(ctx context.Context) *Request {\n\trq := GetRequest(ctx)\n\tif rq != nil {\n\t\treturn rq\n\t}\n\tpanic(\"no request in context\")\n}\n\nfunc WithDefaultRequest(cmd *cobra.Command) (context.Context, error) {\n\tcr := NewDefaultRequest()\n\tcr.Implicit = true\n\tcr.kubeConfig.Context = nil // --context is global\n\n\t// Handle deprecated namespace flag, but allow it in the list command.\n\tif cmd.Name() != \"list\" {\n\t\tif nsFlag := cmd.Flag(\"namespace\"); nsFlag != nil && nsFlag.Changed {\n\t\t\tns := nsFlag.Value.String()\n\t\t\t*cr.kubeConfig.Namespace = ns\n\t\t\tcr.KubeFlags[\"namespace\"] = ns\n\t\t}\n\t}\n\tctx := cmd.Context()\n\tif err := cr.setGlobalConnectFlags(cmd); err != nil {\n\t\treturn ctx, err\n\t}\n\treturn WithRequest(ctx, cr), nil\n}\n\nfunc WithRequest(ctx context.Context, cr *Request) context.Context {\n\treturn context.WithValue(ctx, requestKey{}, cr)\n}\n\nfunc NewDefaultRequest() *Request {\n\tcr := Request{\n\t\tConnectRequest: &connector.ConnectRequest{\n\t\t\tKubeFlags: make(map[string]string),\n\t\t},\n\t\tkubeConfig: genericclioptions.NewConfigFlags(false),\n\t}\n\tcr.addKubeconfigEnv()\n\treturn &cr\n}\n\nfunc GetKubeStartingConfig(cmd *cobra.Command) (*api.Config, error) {\n\tpathOpts := clientcmd.NewDefaultPathOptions()\n\tif kcFlag := cmd.Flag(\"kubeconfig\"); kcFlag != nil && kcFlag.Changed {\n\t\tpathOpts.ExplicitFileFlag = kcFlag.Value.String()\n\t}\n\treturn pathOpts.GetStartingConfig()\n}\n\nfunc (cr *CobraRequest) GetAllNamespaces(cmd *cobra.Command) ([]string, error) {\n\tif err := cr.CommitFlags(cmd); err != nil {\n\t\treturn nil, err\n\t}\n\trs, err := cr.kubeConfig.ToRESTConfig()\n\tif err != nil {\n\t\treturn nil, errcat.NoDaemonLogs.Newf(\"ToRESTConfig: %v\", err)\n\t}\n\tcs, err := kubernetes.NewForConfig(rs)\n\tif err != nil {\n\t\treturn nil, errcat.NoDaemonLogs.Newf(\"NewForConfig: %v\", err)\n\t}\n\tnsl, err := cs.CoreV1().Namespaces().List(cmd.Context(), v1.ListOptions{})\n\tif err != nil {\n\t\treturn nil, errcat.NoDaemonLogs.Newf(\"Namespaces.List: %v\", err)\n\t}\n\titms := nsl.Items\n\tnss := make([]string, len(itms))\n\tfor i, itm := range itms {\n\t\tnss[i] = itm.Name\n\t}\n\treturn nss, nil\n}\n\nfunc (cr *CobraRequest) autocompleteNamespace(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tclog.Debugf(cmd.Context(), \"autocompleteNamespace %q\", toComplete)\n\tvar stripFunc func(s string) bool\n\tif toComplete != \"\" {\n\t\tstripFunc = func(s string) bool { return !strings.HasPrefix(s, toComplete) }\n\t}\n\treturn cr.autocompleteNamespaceFunc(cmd, stripFunc)\n}\n\nfunc (cr *CobraRequest) autocompleteNamespaces(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tvar stripFunc func(s string) bool\n\tvar pfx string\n\tif toComplete != \"\" {\n\t\tfound := strings.Split(toComplete, \",\")\n\t\tll := len(found) - 1\n\t\tlast := found[ll]\n\t\tif ll > 0 {\n\t\t\tpfx = strings.Join(found[:ll], \",\") + \",\"\n\t\t\tif last != \"\" {\n\t\t\t\tstripFunc = func(s string) bool { return slices.Contains(found, s) || !strings.HasPrefix(s, last) }\n\t\t\t} else {\n\t\t\t\tstripFunc = func(s string) bool { return slices.Contains(found, s) }\n\t\t\t}\n\t\t} else {\n\t\t\tstripFunc = func(s string) bool { return !strings.HasPrefix(s, last) }\n\t\t}\n\t}\n\tnss, d := cr.autocompleteNamespaceFunc(cmd, stripFunc)\n\tif pfx != \"\" {\n\t\tfor i, ns := range nss {\n\t\t\tnss[i] = pfx + ns\n\t\t}\n\t}\n\treturn nss, d\n}\n\nfunc (cr *CobraRequest) autocompleteNamespaceFunc(cmd *cobra.Command, stripFunc func(s string) bool) ([]string, cobra.ShellCompDirective) {\n\tctx := cmd.Context()\n\tnss, err := cr.GetAllNamespaces(cmd)\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tif stripFunc != nil {\n\t\tnss = slices.DeleteFunc(nss, stripFunc)\n\t}\n\treturn nss, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc (cr *CobraRequest) autocompleteCluster(cmd *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\tctx := cmd.Context()\n\tconfig, err := cr.GetConfig(cmd)\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\n\tcxl := config.Clusters\n\tcs := make([]string, len(cxl))\n\ti := 0\n\tfor n := range cxl {\n\t\tcs[i] = n\n\t\ti++\n\t}\n\treturn cs, cobra.ShellCompDirectiveNoFileComp\n}\n\nfunc (cr *CobraRequest) GetConfig(cmd *cobra.Command) (*api.Config, error) {\n\tif err := cr.CommitFlags(cmd); err != nil {\n\t\treturn nil, err\n\t}\n\tcfg, err := GetKubeStartingConfig(cmd)\n\tif err != nil {\n\t\treturn nil, errcat.NoDaemonLogs.Newf(\"GetKubeStartingConfig: %v\", err)\n\t}\n\treturn cfg, nil\n}\n"
  },
  {
    "path": "pkg/client/cli/daemon/request_test.go",
    "content": "package daemon\n\nimport (\n\t\"net/netip\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n)\n\nfunc Test_parseSubnetViaWorkload(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tdps     string\n\t\twant    prefixViaWL\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\t\"empty\",\n\t\t\t\"\",\n\t\t\tprefixViaWL{},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"workload with dot\",\n\t\t\t\"127.1.2.3/32=workload.namespace\",\n\t\t\tprefixViaWL{},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"invalid subnet\",\n\t\t\t\"bad=workload\",\n\t\t\tprefixViaWL{},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"ok\",\n\t\t\t\"127.1.2.3/32=workload\",\n\t\t\tprefixViaWL{\n\t\t\t\tsubnet:   netip.MustParsePrefix(\"127.1.2.3/32\"),\n\t\t\t\tworkload: \"workload\",\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"all\",\n\t\t\t\"all=workload\",\n\t\t\tprefixViaWL{\n\t\t\t\tsymbolic: \"all\",\n\t\t\t\tworkload: \"workload\",\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"also\",\n\t\t\t\"also=workload\",\n\t\t\tprefixViaWL{\n\t\t\t\tsymbolic: \"also\",\n\t\t\t\tworkload: \"workload\",\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"pods\",\n\t\t\t\"pods=workload\",\n\t\t\tprefixViaWL{\n\t\t\t\tsymbolic: \"pods\",\n\t\t\t\tworkload: \"workload\",\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"service\",\n\t\t\t\"service=workload\",\n\t\t\tprefixViaWL{\n\t\t\t\tsymbolic: \"service\",\n\t\t\t\tworkload: \"workload\",\n\t\t\t},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"other\",\n\t\t\t\"other=workload\",\n\t\t\tprefixViaWL{},\n\t\t\ttrue,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := parseSubnetViaWorkload(tt.dps)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"parseDomainProxy(%q) error = %v, wantErr %v\", tt.dps, err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"parseDomainProxy(%q) got = %v, want %v\", tt.dps, got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_parseProxyVias(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tproxyVia []string\n\t\twant     []*daemon.SubnetViaWorkload\n\t\twantErr  bool\n\t}{\n\t\t{\n\t\t\tname:     \"single\",\n\t\t\tproxyVia: []string{\"127.1.2.0/24=workload\"},\n\t\t\twant: []*daemon.SubnetViaWorkload{{\n\t\t\t\tSubnet:   \"127.1.2.0/24\",\n\t\t\t\tWorkload: \"workload\",\n\t\t\t}},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"multi\",\n\t\t\tproxyVia: []string{\"127.1.2.0/24=workload1\", \"127.1.3.0/24=workload2\"},\n\t\t\twant: []*daemon.SubnetViaWorkload{\n\t\t\t\t{\n\t\t\t\t\tSubnet:   \"127.1.2.0/24\",\n\t\t\t\t\tWorkload: \"workload1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSubnet:   \"127.1.3.0/24\",\n\t\t\t\t\tWorkload: \"workload2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"multi-overlap\",\n\t\t\tproxyVia: []string{\"127.1.2.0/16=workload1\", \"127.1.3.0/16=workload2\"},\n\t\t\twant:     nil,\n\t\t\twantErr:  true,\n\t\t},\n\t\t{\n\t\t\tname:     \"symbolic-overlap\",\n\t\t\tproxyVia: []string{\"also=workload1\", \"also=workload2\"},\n\t\t\twant:     nil,\n\t\t\twantErr:  true,\n\t\t},\n\t\t{\n\t\t\tname:     \"symbolic-overlap-all\",\n\t\t\tproxyVia: []string{\"also=workload1\", \"all=workload2\"},\n\t\t\twant:     nil,\n\t\t\twantErr:  true,\n\t\t},\n\t\t{\n\t\t\tname:     \"multi-mixed\",\n\t\t\tproxyVia: []string{\"127.1.2.0/16=workload1\", \"also=workload2\"},\n\t\t\twant: []*daemon.SubnetViaWorkload{\n\t\t\t\t{\n\t\t\t\t\tSubnet:   \"127.1.2.0/16\",\n\t\t\t\t\tWorkload: \"workload1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSubnet:   \"also\",\n\t\t\t\t\tWorkload: \"workload2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:     \"multi-mixed-all\",\n\t\t\tproxyVia: []string{\"127.1.2.0/16=workload1\", \"all=workload2\"},\n\t\t\twant: []*daemon.SubnetViaWorkload{\n\t\t\t\t{\n\t\t\t\t\tSubnet:   \"127.1.2.0/16\",\n\t\t\t\t\tWorkload: \"workload1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSubnet:   \"also\",\n\t\t\t\t\tWorkload: \"workload2\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSubnet:   \"pods\",\n\t\t\t\t\tWorkload: \"workload2\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tSubnet:   \"service\",\n\t\t\t\t\tWorkload: \"workload2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := parseProxyVias(tt.proxyVia)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"parseProxyVias() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"parseProxyVias() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/daemon/userd.go",
    "content": "package daemon\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net/netip\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc\"\n\tgrpcCodes \"google.golang.org/grpc/codes\"\n\tgrpcStatus \"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n)\n\ntype UserClient interface {\n\tconnector.ConnectorClient\n\tio.Closer\n\tConn() *grpc.ClientConn\n\tContainerized() bool\n\tDaemonPort() int\n\tDaemonID() *Identifier\n\tExecutable() string\n\tDaemonInfo() *Info\n\tLookup(ctx context.Context, addr string) (netip.Addr, error)\n\tName() string\n\tSemver() semver.Version\n\tAddHandler(ctx context.Context, id string, cmd *exec.Cmd, containerName string) error\n\tSetConnectionInfo(name string, clusterContext string, namespace string)\n}\n\ntype userClient struct {\n\tconnector.ConnectorClient\n\tconn       *grpc.ClientConn\n\tinfo       *Info\n\tversion    semver.Version\n\texecutable string\n\tname       string\n}\n\nvar NewUserClientFunc = NewUserClient //nolint:gochecknoglobals // extension point\n\nfunc NewUserClient(conn *grpc.ClientConn, info *Info, version semver.Version, name string, executable string) UserClient {\n\treturn &userClient{ConnectorClient: connector.NewConnectorClient(conn), conn: conn, info: info, version: version, name: name, executable: executable}\n}\n\ntype Session struct {\n\tUserClient\n\tInfo    *connector.ConnectInfo\n\tStarted bool\n}\n\ntype userDaemonKey struct{}\n\nfunc GetUserClient(ctx context.Context) UserClient {\n\tif ud, ok := ctx.Value(userDaemonKey{}).(UserClient); ok {\n\t\treturn ud\n\t}\n\treturn nil\n}\n\nfunc MustGetUserClient(ctx context.Context) UserClient {\n\tud := GetUserClient(ctx)\n\tif ud == nil {\n\t\tpanic(\"no user client in context\")\n\t}\n\treturn ud\n}\n\nfunc WithUserClient(ctx context.Context, ud UserClient) context.Context {\n\treturn context.WithValue(ctx, userDaemonKey{}, ud)\n}\n\ntype sessionKey struct{}\n\nfunc GetSession(ctx context.Context) *Session {\n\tif s, ok := ctx.Value(sessionKey{}).(*Session); ok {\n\t\treturn s\n\t}\n\treturn nil\n}\n\nfunc MustGetSession(ctx context.Context) *Session {\n\ts := GetSession(ctx)\n\tif s == nil {\n\t\tpanic(\"no session in context\")\n\t}\n\treturn s\n}\n\nfunc WithSession(ctx context.Context, s *Session) context.Context {\n\treturn context.WithValue(ctx, sessionKey{}, s)\n}\n\nfunc (u *userClient) Close() error {\n\treturn u.conn.Close()\n}\n\nfunc (u *userClient) Conn() *grpc.ClientConn {\n\treturn u.conn\n}\n\nfunc (u *userClient) DaemonInfo() *Info {\n\treturn u.info\n}\n\nfunc (u *userClient) Containerized() bool {\n\treturn u.info != nil && u.info.InDocker()\n}\n\nfunc (u *userClient) DaemonID() *Identifier {\n\treturn u.info.DaemonID()\n}\n\nfunc (u *userClient) Executable() string {\n\treturn u.executable\n}\n\nfunc (u *userClient) Lookup(ctx context.Context, name string) (addr netip.Addr, err error) {\n\tipb, err := u.LookupIP(ctx, &daemon.LookupIPRequest{Name: name})\n\tif err != nil {\n\t\treturn addr, errcat.User.Errorf(tpGrpc.FromGRPC(err), \"unable to resolve name %q\", name)\n\t}\n\terr = addr.UnmarshalBinary(ipb.Ip)\n\tif err != nil {\n\t\treturn addr, errcat.NoDaemonLogs.New(err)\n\t}\n\treturn addr, nil\n}\n\nfunc (u *userClient) Name() string {\n\treturn u.name\n}\n\nfunc (u *userClient) Semver() semver.Version {\n\treturn u.version\n}\n\nfunc (u *userClient) DaemonPort() int {\n\tif u.info.InDocker() {\n\t\taddr := u.conn.Target()\n\t\tif lc := strings.LastIndexByte(addr, ':'); lc >= 0 {\n\t\t\tif port, err := strconv.Atoi(addr[lc+1:]); err == nil {\n\t\t\t\treturn port\n\t\t\t}\n\t\t}\n\t}\n\treturn -1\n}\n\nfunc (u *userClient) SetConnectionInfo(name string, clusterContext string, namespace string) {\n\tu.info.SetConnectionInfo(name, clusterContext, namespace)\n}\n\nfunc (u *userClient) AddHandler(ctx context.Context, id string, cmd *exec.Cmd, containerName string) error {\n\t// setup cleanup for the handler process\n\tior := connector.Interceptor{\n\t\tInterceptId:   id,\n\t\tPid:           int32(cmd.Process.Pid),\n\t\tContainerName: containerName,\n\t}\n\n\t// Send info about the pid and intercept id to the traffic-manager so that it kills\n\t// the process if it receives a leave of quit call.\n\tif _, err := u.AddInterceptor(ctx, &ior); err != nil {\n\t\tswitch grpcStatus.Code(err) {\n\t\tcase grpcCodes.NotFound, grpcCodes.Canceled:\n\t\t\t// The intercept was already deleted or deactivation was caused by a disconnect\n\t\t\tclog.Infof(ctx, \"intercept no longer present when adding container %s as interceptor\", containerName)\n\t\t\terr = nil\n\t\tdefault:\n\t\t\terr = tpGrpc.FromGRPC(err)\n\t\t\tclog.Errorf(ctx, \"error adding process with pid %d as interceptor: %v\", ior.Pid, err)\n\t\t}\n\t\t_ = cmd.Process.Kill()\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *Session) GetAgentConfig(ctx context.Context, workload string) (*agentconfig.Sidecar, error) {\n\tagc, err := s.UserClient.GetAgentConfig(ctx, &manager.AgentConfigRequest{Name: workload})\n\tif err != nil {\n\t\treturn nil, tpGrpc.FromGRPC(err)\n\t}\n\treturn agentconfig.UnmarshalYAML(agc.Data)\n}\n\nfunc (s *Session) GetRootClientConfig() (client.Config, error) {\n\treturn GetRootClientConfig(s.Info.GetDaemonStatus())\n}\n\nfunc GetRootClientConfig(ds *daemon.DaemonStatus) (client.Config, error) {\n\tdata := ds.GetOutboundConfig().GetClientConfig()\n\tif data == nil {\n\t\treturn nil, errors.New(\"no outbound config\")\n\t}\n\treturn client.UnmarshalJSONConfig(data, false)\n}\n\n// GetCommandKubeConfig will return the fully resolved client.Kubeconfig for the given command.\nfunc GetCommandKubeConfig(cmd *cobra.Command) (*k8s.Kubeconfig, error) {\n\tctx := cmd.Context()\n\tuc := GetUserClient(ctx)\n\tvar kc *k8s.Kubeconfig\n\tvar err error\n\tif uc != nil && !cmd.Flag(\"context\").Changed {\n\t\t// Get the context that we're currently connected to.\n\t\tvar ci *connector.ConnectInfo\n\t\tci, err = uc.Status(ctx, &emptypb.Empty{})\n\t\tif err == nil {\n\t\t\tkc, err = k8s.NewKubeconfig(ctx, false, map[string]string{\"context\": ci.ClusterContext}, \"\", nil)\n\t\t} else {\n\t\t\terr = tpGrpc.FromGRPC(err)\n\t\t}\n\t} else {\n\t\tif GetRequest(ctx) == nil {\n\t\t\tif ctx, err = WithDefaultRequest(cmd); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\trq := MustGetRequest(ctx)\n\t\tkc, err = k8s.NewKubeconfig(ctx, false, rq.KubeFlags, rq.ManagerNamespace, rq.KubeconfigData)\n\t}\n\treturn kc, err\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/auto_complete.go",
    "content": "package docker\n\nimport (\n\t\"bytes\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n)\n\nvar directiveCodeRx = regexp.MustCompile(`^:(\\d)$`) //nolint:gochecknoglobals // constant\n\nfunc AutocompleteRun(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\targs = slices.Insert(args, 0, \"__completeNoDesc\", \"run\")\n\targs = append(args, toComplete)\n\tcc := exec.CommandContext(cmd.Context(), docker.Exe, args...)\n\tcc.Env = os.Environ()\n\tob := bytes.Buffer{}\n\tcc.Stdout = &ob\n\tif err := cc.Run(); err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tnames := strings.Fields(ob.String())\n\tif ln := len(names) - 1; ln >= 0 {\n\t\t// The last name is the directive in the form of a colon followed by a digit.\n\t\tif m := directiveCodeRx.FindStringSubmatch(names[ln]); m != nil {\n\t\t\tn, _ := strconv.Atoi(m[1])\n\t\t\treturn names[:ln], cobra.ShellCompDirective(n)\n\t\t}\n\t}\n\treturn nil, cobra.ShellCompDirectiveError\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/compose/config.go",
    "content": "package compose\n\nimport (\n\t\"context\"\n\t_ \"embed\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/compose-spec/compose-go/v2/cli\"\n\t\"github.com/compose-spec/compose-go/v2/loader\"\n\tcompose \"github.com/compose-spec/compose-go/v2/types\"\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/cmd/cobraparser/v2/generate\"\n\t\"github.com/telepresenceio/telepresence/cmd/cobraparser/v2/types\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nconst (\n\textensionKey = \"x-tele\"\n)\n\ntype parentConfig struct {\n\t*topLevelExtension\n\n\t// Compose options\n\tprojectDir  string\n\tprojectName string\n\tprogress    string\n\tconfigPaths []string\n\tenvFiles    []string\n\tprofiles    []string\n\n\tmustBeConnected bool\n\tservices        []string\n\tcommandFlags    *pflag.FlagSet\n\texistingProject *compose.Project\n}\n\ntype config struct {\n\ttopLevelExtension\n\t*parentConfig\n\tsubCommandFlags *pflag.FlagSet\n}\n\nfunc GenerateSubCommands(cmd *cobra.Command) []*cobra.Command {\n\tpc := &parentConfig{}\n\tuf := cmd.UsageFunc()\n\tcmd.SetUsageFunc(func(*cobra.Command) error {\n\t\tcmd.SetContext(flags.WithFlagSets(cmd.Context(), pc.commandFlags))\n\t\treturn uf(cmd)\n\t})\n\tpc.addComposeFlags(cmd)\n\tcommands := make([]*cobra.Command, len(dockerComposeCLI.Subcommands))\n\tfor i, subCmd := range dockerComposeCLI.Subcommands {\n\t\tc := &config{parentConfig: pc}\n\t\tsc := c.subCommand(&subCmd)\n\t\tif dryRun := sc.Flags().Lookup(\"dry-run\"); dryRun != nil {\n\t\t\tdryRun.Hidden = true\n\t\t}\n\t\tcommands[i] = sc\n\t}\n\treturn commands\n}\n\n// toProjectOptions was shamelessly copied from https://github.com/docker/compose/blob/main/cmd/compose/compose.go.\n// Kudos to the Docker Compose CLI authors.\nfunc (pc *parentConfig) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {\n\treturn cli.NewProjectOptions(pc.configPaths,\n\t\tappend(po,\n\t\t\tcli.WithWorkingDirectory(pc.projectDir),\n\t\t\t// First, apply os.Environment, always win.\n\t\t\tcli.WithOsEnv,\n\t\t\t// Load PWD/.env if present and no explicit --env-file has been set.\n\t\t\tcli.WithEnvFiles(pc.envFiles...),\n\t\t\t// read the dot-env file to populate the project environment\n\t\t\tcli.WithDotEnv,\n\t\t\t// get the compose-file path set by COMPOSE_FILE\n\t\t\tcli.WithConfigFileEnv,\n\t\t\t// if none was selected, get the default compose.yaml file from the current dir or parent folder\n\t\t\tcli.WithDefaultConfigPath,\n\t\t\t// ... and then, a project directory != PWD maybe has been set, so let's load the .env file\n\t\t\tcli.WithEnvFiles(pc.envFiles...),\n\t\t\tcli.WithDotEnv,\n\t\t\t// eventually COMPOSE_PROFILES should have been set\n\t\t\tcli.WithDefaultProfiles(pc.profiles...),\n\t\t\tcli.WithName(pc.projectName))...)\n}\n\nvar dockerComposeCLI types.CommandInfo //nolint:gochecknoglobals // this is a constant\n\n//go:embed dc-cli.json\nvar dcCli []byte\n\nfunc init() {\n\terr := json.Unmarshal(dcCli, &dockerComposeCLI)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc (pc *parentConfig) addComposeFlags(cmd *cobra.Command) {\n\tcfs := cmd.Flags()\n\tcFlags := pflag.NewFlagSet(\"Compose flags\", pflag.ContinueOnError)\n\tcFlags.StringArrayVarP(&pc.configPaths, \"file\", \"f\", []string{}, \"Compose configuration files\")\n\tcFlags.StringArrayVar(&pc.envFiles, \"env-file\", []string{}, \"Optional environment files\")\n\tcFlags.StringVar(&pc.projectDir, \"project-directory\", \"\", \"Specify an alternate working directory (default: the path of the, first specified, Compose file)\")\n\tcFlags.StringVar(&pc.projectName, \"project-name\", \"\", \"Project name\")\n\tcFlags.StringArrayVar(&pc.profiles, \"profile\", []string{}, \"Profile to enable\")\n\tcfs.AddFlagSet(cFlags)\n\tuf := cmd.UsageFunc()\n\tcmd.SetUsageFunc(func(*cobra.Command) error {\n\t\tcmd.SetContext(flags.WithFlagSets(cmd.Context(), cFlags))\n\t\treturn uf(cmd)\n\t})\n\tpc.commandFlags = cFlags\n}\n\nfunc (c *config) subCommand(subCmd *types.CommandInfo) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:   fmt.Sprintf(\"%s [flags] [services]\", subCmd.Name),\n\t\tArgs:  cobra.ArbitraryArgs,\n\t\tShort: subCmd.Description,\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tc.services = args\n\t\t\treturn c.run(cmd)\n\t\t},\n\t\tValidArgsFunction: func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {\n\t\t\tdir := cobra.ShellCompDirectiveNoFileComp\n\t\t\tif slices.Contains(os.Args, \"--\") {\n\t\t\t\tdir = cobra.ShellCompDirectiveDefault\n\t\t\t}\n\t\t\treturn nil, dir\n\t\t},\n\t}\n\tc.subCommandFlags = generate.FlagSet(fmt.Sprintf(\"Compose %s flags\", subCmd.Name), subCmd)\n\tcmdFlags := cmd.Flags()\n\terr := flags.AddUnique(cmdFlags, c.subCommandFlags)\n\tif err != nil {\n\t\tioutil.Printf(os.Stderr, \"internal error: %v\\n\", err)\n\t\tos.Exit(1)\n\t}\n\t// Stop parsing flags at the first non-flag argument (the service name). This mirrors how\n\t// docker compose works, so that e.g. `telepresence compose exec svc wget -qO- <url>`\n\t// doesn't try to interpret `-qO-` as flags for `exec`.\n\tcmdFlags.SetInterspersed(false)\n\tuf := cmd.UsageFunc()\n\tcmd.SetUsageFunc(func(*cobra.Command) error {\n\t\tcmd.SetContext(flags.WithFlagSets(cmd.Context(), c.commandFlags, c.subCommandFlags))\n\t\treturn uf(cmd)\n\t})\n\treturn cmd\n}\n\nfunc (c *config) detached() bool {\n\tif f := c.subCommandFlags.Lookup(\"detach\"); f != nil {\n\t\treturn f.Changed && f.Value.String() == \"true\"\n\t}\n\treturn false\n}\n\nfunc (c *config) appendFlags(flags *pflag.FlagSet, opts []string) []string {\n\t// Need VisitAll here because Visit doesn't use the Changed status of the actual flag, instead\n\t// it keeps track of flags set in the command's FlagSet.\n\tflags.VisitAll(func(f *pflag.Flag) {\n\t\tif !f.Changed {\n\t\t\treturn\n\t\t}\n\t\tfv := f.Value\n\t\tswitch fv.Type() {\n\t\tcase \"bool\":\n\t\t\tv := \"--\" + f.Name\n\t\t\tif fv.String() == \"false\" {\n\t\t\t\tv += \"=false\"\n\t\t\t}\n\t\t\topts = append(opts, v)\n\t\tcase \"stringArray\":\n\t\t\tsv := fv.(pflag.SliceValue)\n\t\t\topt := \"--\" + f.Name\n\t\t\tfor _, v := range sv.GetSlice() {\n\t\t\t\topts = append(opts, opt, v)\n\t\t\t}\n\t\tdefault:\n\t\t\topts = append(opts, \"--\"+f.Name, fv.String())\n\t\t}\n\t})\n\treturn opts\n}\n\nfunc (c *config) run(cmd *cobra.Command) (err error) {\n\tif dryFlag := cmd.Flag(\"dry-run\"); dryFlag != nil && dryFlag.Changed {\n\t\t// A dry-run is impossible, because Telepresence will have to engage with a workload to get\n\t\t// the data needed to modify the docker compose project. The intercept, replace, ingest, and\n\t\t// wiretap will all install a traffic-agent and cannot be considered a dry-run.\n\t\treturn errcat.User.New(\"--dry-run is not supported\")\n\t}\n\n\tc.progress = cmd.Flag(global.FlagProgress).Value.String()\n\tname := cmd.Name()\n\tc.mustBeConnected = true\n\tswitch name {\n\tcase \"build\", \"create\", \"start\", \"up\":\n\t\tc.mustBeConnected = false\n\tcase \"ls\", \"version\":\n\t\treturn c.dispatchToCompose(cmd.Context(), name)\n\t}\n\n\tctx, err := daemon.WithDefaultRequest(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Any resources created here must be canceled when this function returns.\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\tw := progress.NewWriter(cmd.OutOrStdout(), cmd.ErrOrStderr(), progress.Mode(c.progress))\n\n\t// Tell the underlying framework to keep quiet.\n\tctx = progress.WithContextWriter(ctx, w)\n\tdefer func() {\n\t\tprogress.Stop(ctx)\n\t}()\n\n\ttr, err := c.loadProject(ctx)\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\tes := tr.serviceExtensions()\n\tif len(es) == 0 {\n\t\treturn tr.runCommand(ctx, name)\n\t}\n\n\tconnections := make(map[string]*connection)\n\tif name == \"down\" {\n\t\tdefer func() {\n\t\t\tprogress.Start(ctx, \"Disconnecting\")\n\t\t\tfor _, cc := range connections {\n\t\t\t\tcc.disconnect()\n\t\t\t}\n\t\t}()\n\t}\n\n\tprogress.Start(ctx, \"Connecting\")\n\texistingComposeFile, err := c.connect(ctx, es, connections)\n\tif err != nil {\n\t\tif c.mustBeConnected && errors.Is(err, daemon.ErrNoUserDaemon) {\n\t\t\t// The daemon is not running, although the command expects it to. This means that no services should be running either.\n\t\t\t// So let's just run the command without any extensions so that docker compose produces the expected error output.\n\t\t\terr = tr.runCommand(ctx, name)\n\t\t}\n\t\treturn err\n\t}\n\n\tif existingComposeFile != \"\" {\n\t\tclog.Debugf(ctx, \"Existing compose file: %s\", existingComposeFile)\n\t\tp, err := loadExistingProject(ctx, c.projectDir, existingComposeFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tc.existingProject = p\n\t}\n\tg := log.NewGroup(ctx)\n\taesCh := make(chan *engagement, len(es))\n\tprogress.Start(ctx, \"Engaging\")\n\tfor _, e := range es {\n\t\tg.Go(e.composeService().Name, func(ctx context.Context) error { return tr.engage(ctx, e, aesCh) })\n\t}\n\n\tg.Go(\"compose\", func(ctx context.Context) error {\n\t\tfor i := len(es); i > 0; i-- {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn nil\n\t\t\tcase ae := <-aesCh:\n\t\t\t\ttr.addEngagement(ae)\n\t\t\t}\n\t\t}\n\t\tprogress.Stop(ctx)\n\t\tif name == \"create\" || name == \"stop\" || name == \"up\" && !c.detached() {\n\t\t\tdefer tr.disengage(ctx)\n\t\t}\n\t\treturn tr.runCommand(ctx, name)\n\t})\n\terr = g.Wait()\n\tif err != nil && strings.Contains(err.Error(), \"graceful shutdown\") {\n\t\terr = nil\n\t}\n\treturn err\n}\n\nfunc (c *config) connect(ctx context.Context, es map[string]serviceExtension, connections map[string]*connection) (existingComposeFile string, err error) {\n\tfor _, e := range es {\n\t\tvar cc *connectionConfig\n\t\tcc, err = c.getConnectionConfig(e.connectionName())\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tcx, ok := connections[cc.Name]\n\t\tif !ok {\n\t\t\tcx, err = cc.Connect(ctx, es, c.mustBeConnected)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\tconnections[cc.Name] = cx\n\t\t}\n\t\tclog.Debugf(ctx, \"Service %q will be %s\", e.composeService().Name, e.engagementType().WorkDone())\n\t\tif existingComposeFile == \"\" {\n\t\t\texistingComposeFile = daemon.MustGetSession(cx).DaemonInfo().ComposeFile\n\t\t}\n\t\te.setConnection(cx)\n\t}\n\treturn existingComposeFile, nil\n}\n\nfunc (c *config) loadProject(ctx context.Context) (*transformer, error) {\n\t// Skip compose-go's built-in validation so that services without a local image (e.g. proxy\n\t// services) can be loaded. Those services are removed or transformed before the final\n\t// compose file is written to disk, at which point docker compose performs its own validation.\n\toptions, err := c.toProjectOptions(cli.WithLoadOptions(loader.WithSkipValidation, func(o *loader.Options) {\n\t\to.SkipConsistencyCheck = true\n\t}))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tp, err := options.LoadProject(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tev, ok := p.Extensions[extensionKey]\n\tif ok {\n\t\terr = c.parse(ev)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\ttr, err := newTransformer(c, p)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(c.profiles) > 0 {\n\t\terr = tr.withProfiles(c.profiles)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn tr, nil\n}\n\nfunc (c *config) dispatchToCompose(ctx context.Context, name string) error {\n\treturn errcat.User.New(proc.StdCommand(ctx, docker.Exe, slices.Insert(c.services, 0, \"compose\", name)...).Run())\n}\n\n//nolint:gochecknoglobals // constant\nvar defaultConnectionConfig = &connectionConfig{\n\tNamespace: \"default\",\n}\n\nfunc (c *config) getConnectionConfig(name string) (*connectionConfig, error) {\n\tif len(c.Connections) == 0 {\n\t\tc.Connections = []*connectionConfig{defaultConnectionConfig}\n\t}\n\tccs := c.Connections\n\tif name == \"\" {\n\t\tif len(ccs) == 1 {\n\t\t\treturn ccs[0], nil\n\t\t}\n\t\treturn nil, errcat.User.New(\"multiple connections found, please specify a connection name\")\n\t}\n\tfor _, cc := range ccs {\n\t\tif cc.Name == name {\n\t\t\treturn cc, nil\n\t\t}\n\t}\n\treturn nil, errcat.User.Newf(\"connection %q not found\", name)\n}\n\nfunc (c *config) getMountPort(e mountsExtension) (uint16, error) {\n\tif !e.needsVolumes() {\n\t\treturn 0, nil\n\t}\n\tif ep := c.existingProject; ep != nil {\n\t\tcn := e.composeService().Name\n\t\tif s, ok := ep.Services[cn]; ok {\n\t\t\tif pa, ok := s.Annotations[mountPortAnnotation]; ok {\n\t\t\t\tif p, err := strconv.Atoi(pa); err == nil {\n\t\t\t\t\tclog.Debugf(e.connection(), \"Found existing mount port %d for %q\", p, cn)\n\t\t\t\t\treturn uint16(p), nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tlma, err := ioutil.FreePortsTCP(1)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn lma[0].Port(), nil\n}\n\nfunc loadExistingProject(ctx context.Context, pwd, path string) (*compose.Project, error) {\n\treturn loader.LoadWithContext(ctx, compose.ConfigDetails{\n\t\tConfigFiles: []compose.ConfigFile{{Filename: path}},\n\t\tWorkingDir:  pwd,\n\t}, func(options *loader.Options) {\n\t\toptions.SkipConsistencyCheck = true\n\t\toptions.SkipValidation = true\n\t\toptions.SkipNormalization = true\n\t\toptions.SkipInterpolation = true\n\t\toptions.SkipResolveEnvironment = true\n\t\toptions.SkipDefaultValues = true\n\t\toptions.SkipExtends = true\n\t\toptions.SkipInclude = true\n\t})\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/compose/connection.go",
    "content": "package compose\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tcompose \"github.com/compose-spec/compose-go/v2/types\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype connectionConfig struct {\n\t// Namespace to connect to.\n\tName             string         `json:\"name,omitempty\"`\n\tNamespace        string         `json:\"namespace,omitempty\"`\n\tAlsoProxy        []netip.Prefix `json:\"also-proxy,omitempty\"`\n\tNeverProxy       []netip.Prefix `json:\"never-proxy,omitempty\"`\n\tManagerNamespace string         `json:\"manager-namespace,omitempty\"`\n\tMappedNamespaces []string       `json:\"mapped-namespaces,omitempty\"`\n}\n\ntype connection struct {\n\tcontext.Context\n\t*connectionConfig\n\tdnsIP   netip.Addr\n\tsubnets []netip.Prefix\n\tproxies map[string]netip.Addr\n}\n\nfunc composePortString(p *compose.ServicePortConfig) string {\n\tb := strings.Builder{}\n\tif p.HostIP != \"\" {\n\t\tb.WriteString(p.HostIP)\n\t\tb.WriteByte(':')\n\t}\n\tb.WriteString(p.Published)\n\tb.WriteByte(':')\n\tb.WriteString(strconv.Itoa(int(p.Target)))\n\tif p.Protocol != \"\" {\n\t\tb.WriteByte('/')\n\t\tb.WriteString(p.Protocol)\n\t}\n\treturn b.String()\n}\n\nfunc composePortTarget(p *compose.ServicePortConfig) types.PortAndProto {\n\tpp := types.PortAndProto{Port: uint16(p.Target)}\n\tpp.Proto, _ = types.ParseProto(p.Protocol)\n\treturn pp\n}\n\nfunc (cc *connectionConfig) Connect(ctx context.Context, es map[string]serviceExtension, mustPreExist bool) (*connection, error) {\n\tcr := daemon.GetRequest(ctx)\n\tcr = cr.Clone()\n\tcr.Implicit = false\n\tcr.Docker = true\n\tif cc.Namespace != \"default\" {\n\t\tcr.KubeFlags[\"namespace\"] = cc.Namespace\n\t}\n\tcr.Name = cc.Name\n\tif cr.Name == \"\" {\n\t\tcr.Name = \"trn-\" + cc.Namespace\n\t}\n\tcr.ManagerNamespace = cc.ManagerNamespace\n\tif l := len(cc.AlsoProxy); l > 0 {\n\t\tcr.AlsoProxy = make([]string, l)\n\t\tfor i, p := range cc.AlsoProxy {\n\t\t\tcr.AlsoProxy[i] = p.String()\n\t\t}\n\t}\n\tif l := len(cc.NeverProxy); l > 0 {\n\t\tcr.NeverProxy = make([]string, l)\n\t\tfor i, p := range cc.NeverProxy {\n\t\t\tcr.NeverProxy[i] = p.String()\n\t\t}\n\t}\n\tcr.MappedNamespaces = cc.MappedNamespaces\n\n\tfor _, e := range es {\n\t\tif e.engagementType() == types.EngagementTypeProxy && (e.connectionName() == \"\" || e.connectionName() == cc.Name) {\n\t\t\terr := addProxyReroutes(e.(servicePortExtension), cr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\tctx = daemon.WithRequest(ctx, cr)\n\tctx, err := connect.EnsureUserDaemon(ctx, !mustPreExist)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif err != nil && !mustPreExist {\n\t\t\tconnect.Disconnect(ctx)\n\t\t}\n\t}()\n\n\tctx, err = connect.EnsureSession(ctx, \"compose\", true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tds := daemon.MustGetSession(ctx)\n\trootCfg, err := daemon.GetRootClientConfig(ds.Info.DaemonStatus)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to obtain routing info for connection: %w\", err)\n\t}\n\n\tdns, _, err := docker.GetDaemonContainerNetworkInfo(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tproxies, err := cc.resolveProxies(ctx, ds, es)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &connection{Context: ctx, connectionConfig: cc, dnsIP: dns, proxies: proxies, subnets: rootCfg.Routing().Subnets}, nil\n}\n\n// resolveProxies resolves the name of the proxy definition into its remote service IP. This IP will then be\n// made available to other compose services using `extra_hosts: [\"<proxied service name>=<IP>\"]`, so that any\n// reference to the original service instead goes to the remote IP.\nfunc (cc *connectionConfig) resolveProxies(ctx context.Context, ds *daemon.Session, es map[string]serviceExtension) (map[string]netip.Addr, error) {\n\tvar proxies map[string]netip.Addr\n\tfor _, e := range es {\n\t\tif e.engagementType() == types.EngagementTypeProxy && (e.connectionName() == \"\" || e.connectionName() == cc.Name) {\n\t\t\tip, err := ds.Lookup(ctx, e.name())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcn := e.composeService().Name\n\t\t\tif proxies == nil {\n\t\t\t\tproxies = map[string]netip.Addr{cn: ip}\n\t\t\t} else {\n\t\t\t\tproxies[cn] = ip\n\t\t\t}\n\t\t}\n\t}\n\treturn proxies, nil\n}\n\nfunc (c *connection) disconnect() {\n\tctx, cancel := context.WithTimeout(context.WithoutCancel(c), 3*time.Second)\n\tdefer cancel()\n\tconnect.Disconnect(ctx)\n}\n\nfunc addProxyReroutes(e servicePortExtension, cr *daemon.Request) error {\n\taddProxyLocalReroutes(e, cr)\n\treturn addProxyRemoteReroutes(e, cr)\n}\n\n// addProxyLocalReroutes ensures that the ports published in the docker compose file for a proxied service are published\n// by the Telepresence daemon container that facilitates the proxy. This is done using local reroutes.\n//\n// What's added here are essentially `--reroute-local <local port>:<host>:<remote port>` flags.\nfunc addProxyLocalReroutes(e servicePortExtension, cr *daemon.Request) {\n\tports := e.composeService().Ports\n\tfor pi := range ports {\n\t\tp := &ports[pi]\n\t\tcr.ExposedPorts = append(cr.ExposedPorts, composePortString(p))\n\t\tcpt := composePortTarget(p)\n\t\tcr.LocalReroutes = append(cr.LocalReroutes, fmt.Sprintf(\"%d:%s:%d\", cpt.Port, e.name(), cpt.Port))\n\t}\n}\n\n// addProxyRemoteReroutes ensures that the Telepresence daemon container reroutes the container ports of a proxy to\n// their corresponding remote service ports. Other containers will reach this service using DNS, so a lookup for\n// `<service-name>:<container-port>` must be rerouted to `<service-name>:<service-port>`.\n//\n// What's added here are essentially `--reroute-remote <host>:<remote port>:<new port>` flags.\nfunc addProxyRemoteReroutes(e servicePortExtension, cr *daemon.Request) error {\n\tb := strings.Builder{}\n\tfor _, p := range e.servicePorts() {\n\t\t_, s, _ := p.From().ProtoAndNameOrNumber()\n\t\tif s != \"\" {\n\t\t\treturn errcat.User.Newf(\"port %s is not a number in proxy for %s\", s, e.composeService().Name)\n\t\t}\n\t\tb.Reset()\n\t\tb.WriteString(e.name())\n\t\tb.WriteByte(':')\n\t\tpis := p.ToAsIntOrStr()\n\t\tb.WriteString(pis.String())\n\t\tb.WriteByte(':')\n\t\tb.WriteString(p.From().String())\n\t\tcr.RemoteReroutes = append(cr.RemoteReroutes, b.String())\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/compose/dc-cli.json",
    "content": "{\n  \"name\": \"compose\",\n  \"usage\": \"docker compose [OPTIONS] COMMAND\",\n  \"description\": \"Define and run multi-container applications with Docker\",\n  \"flags\": [\n    {\n      \"name\": \"all-resources\",\n      \"type\": \"bool\",\n      \"description\": \"Include all resources, even those not used by services\"\n    },\n    {\n      \"name\": \"ansi\",\n      \"type\": \"string\",\n      \"description\": \"Control when to print ANSI control characters (\\\"never\\\"|\\\"always\\\"|\\\"auto\\\") (default \\\"auto\\\")\"\n    },\n    {\n      \"name\": \"compatibility\",\n      \"type\": \"bool\",\n      \"description\": \"Run compose in backward compatibility mode\"\n    },\n    {\n      \"name\": \"dry-run\",\n      \"type\": \"bool\",\n      \"description\": \"Execute command in dry run mode\"\n    },\n    {\n      \"name\": \"env-file\",\n      \"type\": \"stringArray\",\n      \"description\": \"Specify an alternate environment file\"\n    },\n    {\n      \"name\": \"file\",\n      \"shorthand\": \"f\",\n      \"type\": \"stringArray\",\n      \"description\": \"Compose configuration files\"\n    },\n    {\n      \"name\": \"parallel\",\n      \"type\": \"int\",\n      \"description\": \"Control max parallelism, -1 for unlimited (default -1)\"\n    },\n    {\n      \"name\": \"profile\",\n      \"type\": \"stringArray\",\n      \"description\": \"Specify a profile to enable\"\n    },\n    {\n      \"name\": \"progress\",\n      \"type\": \"string\",\n      \"description\": \"Set type of progress output (auto, tty, plain, json, quiet)\"\n    },\n    {\n      \"name\": \"project-directory\",\n      \"type\": \"string\",\n      \"description\": \"Specify an alternate working directory (default: the path of the, first specified, Compose file)\"\n    },\n    {\n      \"name\": \"project-name\",\n      \"shorthand\": \"p\",\n      \"type\": \"string\",\n      \"description\": \"Project name\"\n    }\n  ],\n  \"subcommands\": [\n    {\n      \"name\": \"bridge\",\n      \"usage\": \"docker compose bridge [OPTIONS] COMMAND\",\n      \"description\": \"Convert compose files into another model\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        }\n      ],\n      \"subcommands\": [\n        {\n          \"name\": \"transformations\",\n          \"usage\": \"docker compose bridge transformations [OPTIONS] COMMAND\",\n          \"description\": \"Manage transformation images\",\n          \"flags\": [\n            {\n              \"name\": \"dry-run\",\n              \"type\": \"bool\",\n              \"description\": \"Execute command in dry run mode\"\n            }\n          ],\n          \"subcommands\": [\n            {\n              \"name\": \"create\",\n              \"usage\": \"docker compose bridge transformations create [OPTION] PATH\",\n              \"description\": \"Create a new transformation\",\n              \"flags\": [\n                {\n                  \"name\": \"dry-run\",\n                  \"type\": \"bool\",\n                  \"description\": \"Execute command in dry run mode\"\n                },\n                {\n                  \"name\": \"from\",\n                  \"shorthand\": \"f\",\n                  \"type\": \"string\",\n                  \"description\": \"Existing transformation to copy (default: docker/compose-bridge-kubernetes)\"\n                }\n              ]\n            },\n            {\n              \"name\": \"list\",\n              \"usage\": \"docker compose bridge transformations list\",\n              \"description\": \"List available transformations\",\n              \"flags\": [\n                {\n                  \"name\": \"dry-run\",\n                  \"type\": \"bool\",\n                  \"description\": \"Execute command in dry run mode\"\n                },\n                {\n                  \"name\": \"format\",\n                  \"type\": \"string\",\n                  \"description\": \"Format the output. Values: [table | json] (default \\\"table\\\")\"\n                },\n                {\n                  \"name\": \"quiet\",\n                  \"shorthand\": \"q\",\n                  \"type\": \"bool\",\n                  \"description\": \"Only display transformer names\"\n                }\n              ]\n            }\n          ]\n        },\n        {\n          \"name\": \"convert\",\n          \"usage\": \"docker compose bridge convert\",\n          \"description\": \"Convert compose files to Kubernetes manifests, Helm charts, or another model\",\n          \"flags\": [\n            {\n              \"name\": \"dry-run\",\n              \"type\": \"bool\",\n              \"description\": \"Execute command in dry run mode\"\n            },\n            {\n              \"name\": \"output\",\n              \"shorthand\": \"o\",\n              \"type\": \"string\",\n              \"description\": \"The output directory for the Kubernetes resources (default \\\"out\\\")\"\n            },\n            {\n              \"name\": \"templates\",\n              \"type\": \"string\",\n              \"description\": \"Directory containing transformation templates\"\n            },\n            {\n              \"name\": \"transformation\",\n              \"shorthand\": \"t\",\n              \"type\": \"stringArray\",\n              \"description\": \"Transformation to apply to compose model (default: docker/compose-bridge-kubernetes)\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"attach\",\n      \"usage\": \"docker compose attach [OPTIONS] SERVICE\",\n      \"description\": \"Attach local standard input, output, and error streams to a service's running container\",\n      \"flags\": [\n        {\n          \"name\": \"detach-keys\",\n          \"type\": \"string\",\n          \"description\": \"Override the key sequence for detaching from a container.\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"index\",\n          \"type\": \"int\",\n          \"description\": \"index of the container if service has multiple replicas.\"\n        },\n        {\n          \"name\": \"no-stdin\",\n          \"type\": \"bool\",\n          \"description\": \"Do not attach STDIN\"\n        },\n        {\n          \"name\": \"sig-proxy\",\n          \"type\": \"bool\",\n          \"description\": \"Proxy all received signals to the process (default true)\"\n        }\n      ]\n    },\n    {\n      \"name\": \"build\",\n      \"usage\": \"docker compose build [OPTIONS] [SERVICE...]\",\n      \"description\": \"Build or rebuild services\",\n      \"flags\": [\n        {\n          \"name\": \"build-arg\",\n          \"type\": \"stringArray\",\n          \"description\": \"Set build-time variables for services\"\n        },\n        {\n          \"name\": \"builder\",\n          \"type\": \"string\",\n          \"description\": \"Set builder to use\"\n        },\n        {\n          \"name\": \"check\",\n          \"type\": \"bool\",\n          \"description\": \"Check build configuration\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"memory\",\n          \"shorthand\": \"m\",\n          \"type\": \"bytes\",\n          \"description\": \"Set memory limit for the build container. Not supported by BuildKit.\"\n        },\n        {\n          \"name\": \"no-cache\",\n          \"type\": \"bool\",\n          \"description\": \"Do not use cache when building the image\"\n        },\n        {\n          \"name\": \"print\",\n          \"type\": \"bool\",\n          \"description\": \"Print equivalent bake file\"\n        },\n        {\n          \"name\": \"provenance\",\n          \"type\": \"string\",\n          \"description\": \"Add a provenance attestation\"\n        },\n        {\n          \"name\": \"pull\",\n          \"type\": \"bool\",\n          \"description\": \"Always attempt to pull a newer version of the image\"\n        },\n        {\n          \"name\": \"push\",\n          \"type\": \"bool\",\n          \"description\": \"Push service images\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Suppress the build output\"\n        },\n        {\n          \"name\": \"sbom\",\n          \"type\": \"string\",\n          \"description\": \"Add a SBOM attestation\"\n        },\n        {\n          \"name\": \"ssh\",\n          \"type\": \"string\",\n          \"description\": \"Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)\"\n        },\n        {\n          \"name\": \"with-dependencies\",\n          \"type\": \"bool\",\n          \"description\": \"Also build dependencies (transitively)\"\n        }\n      ]\n    },\n    {\n      \"name\": \"commit\",\n      \"usage\": \"docker compose commit [OPTIONS] SERVICE [REPOSITORY[:TAG]]\",\n      \"description\": \"Create a new image from a service container's changes\",\n      \"flags\": [\n        {\n          \"name\": \"author\",\n          \"shorthand\": \"a\",\n          \"type\": \"string\",\n          \"description\": \"Author (e.g., \\\"John Hannibal Smith \\u003channibal@a-team.com\\u003e\\\")\"\n        },\n        {\n          \"name\": \"change\",\n          \"shorthand\": \"c\",\n          \"type\": \"list\",\n          \"description\": \"Apply Dockerfile instruction to the created image\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"index\",\n          \"type\": \"int\",\n          \"description\": \"index of the container if service has multiple replicas.\"\n        },\n        {\n          \"name\": \"message\",\n          \"shorthand\": \"m\",\n          \"type\": \"string\",\n          \"description\": \"Commit message\"\n        },\n        {\n          \"name\": \"pause\",\n          \"shorthand\": \"p\",\n          \"type\": \"bool\",\n          \"description\": \"Pause container during commit\",\n          \"default\": \"true\"\n        }\n      ]\n    },\n    {\n      \"name\": \"config\",\n      \"usage\": \"docker compose config [OPTIONS] [SERVICE...]\",\n      \"description\": \"Parse, resolve and render compose file in canonical format\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"environment\",\n          \"type\": \"bool\",\n          \"description\": \"Print environment used for interpolation.\"\n        },\n        {\n          \"name\": \"format\",\n          \"type\": \"string\",\n          \"description\": \"Format the output. Values: [yaml | json]\"\n        },\n        {\n          \"name\": \"hash\",\n          \"type\": \"string\",\n          \"description\": \"Print the service config hash, one per line.\"\n        },\n        {\n          \"name\": \"images\",\n          \"type\": \"bool\",\n          \"description\": \"Print the image names, one per line.\"\n        },\n        {\n          \"name\": \"lock-image-digests\",\n          \"type\": \"bool\",\n          \"description\": \"Produces an override file with image digests\"\n        },\n        {\n          \"name\": \"models\",\n          \"type\": \"bool\",\n          \"description\": \"Print the model names, one per line.\"\n        },\n        {\n          \"name\": \"networks\",\n          \"type\": \"bool\",\n          \"description\": \"Print the network names, one per line.\"\n        },\n        {\n          \"name\": \"no-consistency\",\n          \"type\": \"bool\",\n          \"description\": \"Don't check model consistency - warning: may produce invalid Compose output\"\n        },\n        {\n          \"name\": \"no-env-resolution\",\n          \"type\": \"bool\",\n          \"description\": \"Don't resolve service env files\"\n        },\n        {\n          \"name\": \"no-interpolate\",\n          \"type\": \"bool\",\n          \"description\": \"Don't interpolate environment variables\"\n        },\n        {\n          \"name\": \"no-normalize\",\n          \"type\": \"bool\",\n          \"description\": \"Don't normalize compose model\"\n        },\n        {\n          \"name\": \"no-path-resolution\",\n          \"type\": \"bool\",\n          \"description\": \"Don't resolve file paths\"\n        },\n        {\n          \"name\": \"output\",\n          \"shorthand\": \"o\",\n          \"type\": \"string\",\n          \"description\": \"Save to file (default to stdout)\"\n        },\n        {\n          \"name\": \"profiles\",\n          \"type\": \"bool\",\n          \"description\": \"Print the profile names, one per line.\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Only validate the configuration, don't print anything\"\n        },\n        {\n          \"name\": \"resolve-image-digests\",\n          \"type\": \"bool\",\n          \"description\": \"Pin image tags to digests\"\n        },\n        {\n          \"name\": \"services\",\n          \"type\": \"bool\",\n          \"description\": \"Print the service names, one per line.\"\n        },\n        {\n          \"name\": \"variables\",\n          \"type\": \"bool\",\n          \"description\": \"Print model variables and default values.\"\n        },\n        {\n          \"name\": \"volumes\",\n          \"type\": \"bool\",\n          \"description\": \"Print the volume names, one per line.\"\n        }\n      ]\n    },\n    {\n      \"name\": \"cp\",\n      \"usage\": \"docker compose cp [OPTIONS] SERVICE:SRC_PATH DEST_PATH|-\",\n      \"description\": \"docker compose cp [OPTIONS] SRC_PATH|- SERVICE:DEST_PATH\",\n      \"flags\": [\n        {\n          \"name\": \"all\",\n          \"type\": \"bool\",\n          \"description\": \"Include containers created by the run command\"\n        },\n        {\n          \"name\": \"archive\",\n          \"shorthand\": \"a\",\n          \"type\": \"bool\",\n          \"description\": \"Archive mode (copy all uid/gid information)\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"follow-link\",\n          \"shorthand\": \"L\",\n          \"type\": \"bool\",\n          \"description\": \"Always follow symbol link in SRC_PATH\"\n        },\n        {\n          \"name\": \"index\",\n          \"type\": \"int\",\n          \"description\": \"Index of the container if service has multiple replicas\"\n        }\n      ]\n    },\n    {\n      \"name\": \"create\",\n      \"usage\": \"docker compose create [OPTIONS] [SERVICE...]\",\n      \"description\": \"Creates containers for a service\",\n      \"flags\": [\n        {\n          \"name\": \"build\",\n          \"type\": \"bool\",\n          \"description\": \"Build images before starting containers\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"force-recreate\",\n          \"type\": \"bool\",\n          \"description\": \"Recreate containers even if their configuration and image haven't changed\"\n        },\n        {\n          \"name\": \"no-build\",\n          \"type\": \"bool\",\n          \"description\": \"Don't build an image, even if it's policy\"\n        },\n        {\n          \"name\": \"no-recreate\",\n          \"type\": \"bool\",\n          \"description\": \"If containers already exist, don't recreate them. Incompatible with --force-recreate.\"\n        },\n        {\n          \"name\": \"pull\",\n          \"type\": \"string\",\n          \"description\": \"Pull image before running (\\\"always\\\"|\\\"missing\\\"|\\\"never\\\"|\\\"build\\\") (default \\\"policy\\\")\"\n        },\n        {\n          \"name\": \"quiet-pull\",\n          \"type\": \"bool\",\n          \"description\": \"Pull without printing progress information\"\n        },\n        {\n          \"name\": \"remove-orphans\",\n          \"type\": \"bool\",\n          \"description\": \"Remove containers for services not defined in the Compose file\"\n        },\n        {\n          \"name\": \"scale\",\n          \"type\": \"scale\",\n          \"description\": \"Scale SERVICE to NUM instances. Overrides the scale setting in the Compose file if present.\"\n        },\n        {\n          \"name\": \"yes\",\n          \"shorthand\": \"y\",\n          \"type\": \"bool\",\n          \"description\": \"Assume \\\"yes\\\" as answer to all prompts and run non-interactively\"\n        }\n      ]\n    },\n    {\n      \"name\": \"down\",\n      \"usage\": \"docker compose down [OPTIONS] [SERVICES]\",\n      \"description\": \"Stop and remove containers, networks\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"remove-orphans\",\n          \"type\": \"bool\",\n          \"description\": \"Remove containers for services not defined in the Compose file\"\n        },\n        {\n          \"name\": \"rmi\",\n          \"type\": \"string\",\n          \"description\": \"Remove images used by services. \\\"local\\\" remove only images that don't have a custom tag (\\\"local\\\"|\\\"all\\\")\"\n        },\n        {\n          \"name\": \"timeout\",\n          \"shorthand\": \"t\",\n          \"type\": \"int\",\n          \"description\": \"Specify a shutdown timeout in seconds\"\n        },\n        {\n          \"name\": \"volumes\",\n          \"shorthand\": \"v\",\n          \"type\": \"bool\",\n          \"description\": \"Remove named volumes declared in the \\\"volumes\\\" section of the Compose file and anonymous volumes attached to containers\"\n        }\n      ]\n    },\n    {\n      \"name\": \"events\",\n      \"usage\": \"docker compose events [OPTIONS] [SERVICE...]\",\n      \"description\": \"Receive real time events from containers\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"json\",\n          \"type\": \"bool\",\n          \"description\": \"Output events as a stream of json objects\"\n        },\n        {\n          \"name\": \"since\",\n          \"type\": \"string\",\n          \"description\": \"Show all events created since timestamp\"\n        },\n        {\n          \"name\": \"until\",\n          \"type\": \"string\",\n          \"description\": \"Stream events until this timestamp\"\n        }\n      ]\n    },\n    {\n      \"name\": \"exec\",\n      \"usage\": \"docker compose exec [OPTIONS] SERVICE COMMAND [ARGS...]\",\n      \"description\": \"Execute a command in a running container\",\n      \"flags\": [\n        {\n          \"name\": \"detach\",\n          \"shorthand\": \"d\",\n          \"type\": \"bool\",\n          \"description\": \"Detached mode: Run command in the background\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"env\",\n          \"shorthand\": \"e\",\n          \"type\": \"stringArray\",\n          \"description\": \"Set environment variables\"\n        },\n        {\n          \"name\": \"index\",\n          \"type\": \"int\",\n          \"description\": \"Index of the container if service has multiple replicas\"\n        },\n        {\n          \"name\": \"no-tty\",\n          \"shorthand\": \"T\",\n          \"type\": \"bool\",\n          \"description\": \"Disable pseudo-TTY allocation. By default 'docker compose exec' allocates a TTY. (default true)\"\n        },\n        {\n          \"name\": \"privileged\",\n          \"type\": \"bool\",\n          \"description\": \"Give extended privileges to the process\"\n        },\n        {\n          \"name\": \"user\",\n          \"shorthand\": \"u\",\n          \"type\": \"string\",\n          \"description\": \"Run the command as this user\"\n        },\n        {\n          \"name\": \"workdir\",\n          \"shorthand\": \"w\",\n          \"type\": \"string\",\n          \"description\": \"Path to workdir directory for this command\"\n        }\n      ]\n    },\n    {\n      \"name\": \"export\",\n      \"usage\": \"docker compose export [OPTIONS] SERVICE\",\n      \"description\": \"Export a service container's filesystem as a tar archive\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"index\",\n          \"type\": \"int\",\n          \"description\": \"index of the container if service has multiple replicas.\"\n        },\n        {\n          \"name\": \"output\",\n          \"shorthand\": \"o\",\n          \"type\": \"string\",\n          \"description\": \"Write to a file, instead of STDOUT\"\n        }\n      ]\n    },\n    {\n      \"name\": \"images\",\n      \"usage\": \"docker compose images [OPTIONS] [SERVICE...]\",\n      \"description\": \"List images used by the created containers\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"format\",\n          \"type\": \"string\",\n          \"description\": \"Format the output. Values: [table | json] (default \\\"table\\\")\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Only display IDs\"\n        }\n      ]\n    },\n    {\n      \"name\": \"kill\",\n      \"usage\": \"docker compose kill [OPTIONS] [SERVICE...]\",\n      \"description\": \"Force stop service containers\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"remove-orphans\",\n          \"type\": \"bool\",\n          \"description\": \"Remove containers for services not defined in the Compose file\"\n        },\n        {\n          \"name\": \"signal\",\n          \"shorthand\": \"s\",\n          \"type\": \"string\",\n          \"description\": \"SIGNAL to send to the container\",\n          \"default\": \"\\\"SIGKILL\\\"\"\n        }\n      ]\n    },\n    {\n      \"name\": \"logs\",\n      \"usage\": \"docker compose logs [OPTIONS] [SERVICE...]\",\n      \"description\": \"View output from containers\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"follow\",\n          \"shorthand\": \"f\",\n          \"type\": \"bool\",\n          \"description\": \"Follow log output\"\n        },\n        {\n          \"name\": \"index\",\n          \"type\": \"int\",\n          \"description\": \"index of the container if service has multiple replicas\"\n        },\n        {\n          \"name\": \"no-color\",\n          \"type\": \"bool\",\n          \"description\": \"Produce monochrome output\"\n        },\n        {\n          \"name\": \"no-log-prefix\",\n          \"type\": \"bool\",\n          \"description\": \"Don't print prefix in logs\"\n        },\n        {\n          \"name\": \"since\",\n          \"type\": \"string\",\n          \"description\": \"Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)\"\n        },\n        {\n          \"name\": \"tail\",\n          \"shorthand\": \"n\",\n          \"type\": \"string\",\n          \"description\": \"Number of lines to show from the end of the logs for each container (default \\\"all\\\")\"\n        },\n        {\n          \"name\": \"timestamps\",\n          \"shorthand\": \"t\",\n          \"type\": \"bool\",\n          \"description\": \"Show timestamps\"\n        },\n        {\n          \"name\": \"until\",\n          \"type\": \"string\",\n          \"description\": \"Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)\"\n        }\n      ]\n    },\n    {\n      \"name\": \"ls\",\n      \"usage\": \"docker compose ls [OPTIONS]\",\n      \"description\": \"List running compose projects\",\n      \"flags\": [\n        {\n          \"name\": \"all\",\n          \"shorthand\": \"a\",\n          \"type\": \"bool\",\n          \"description\": \"Show all stopped Compose projects\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"filter\",\n          \"type\": \"filter\",\n          \"description\": \"Filter output based on conditions provided\"\n        },\n        {\n          \"name\": \"format\",\n          \"type\": \"string\",\n          \"description\": \"Format the output. Values: [table | json] (default \\\"table\\\")\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Only display project names\"\n        }\n      ]\n    },\n    {\n      \"name\": \"pause\",\n      \"usage\": \"docker compose pause [SERVICE...]\",\n      \"description\": \"Pause services\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        }\n      ]\n    },\n    {\n      \"name\": \"port\",\n      \"usage\": \"docker compose port [OPTIONS] SERVICE PRIVATE_PORT\",\n      \"description\": \"Print the public port for a port binding\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"index\",\n          \"type\": \"int\",\n          \"description\": \"Index of the container if service has multiple replicas\"\n        },\n        {\n          \"name\": \"protocol\",\n          \"type\": \"string\",\n          \"description\": \"tcp or udp\",\n          \"default\": \"\\\"tcp\\\"\"\n        }\n      ]\n    },\n    {\n      \"name\": \"ps\",\n      \"usage\": \"docker compose ps [OPTIONS] [SERVICE...]\",\n      \"description\": \"List containers\",\n      \"flags\": [\n        {\n          \"name\": \"all\",\n          \"shorthand\": \"a\",\n          \"type\": \"bool\",\n          \"description\": \"Show all stopped containers (including those created by the run command)\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"filter\",\n          \"type\": \"string\",\n          \"description\": \"Filter services by a property (supported filters: status)\"\n        },\n        {\n          \"name\": \"format\",\n          \"type\": \"string\",\n          \"description\": \"Format output using a custom template: 'table':            Print output in table format with column headers (default) 'table TEMPLATE':   Print output in table format using the given Go template 'json':             Print in JSON format 'TEMPLATE':         Print output using the given Go template. Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates (default \\\"table\\\")\"\n        },\n        {\n          \"name\": \"no-trunc\",\n          \"type\": \"bool\",\n          \"description\": \"Don't truncate output\"\n        },\n        {\n          \"name\": \"orphans\",\n          \"type\": \"bool\",\n          \"description\": \"Include orphaned services (not declared by project) (default true)\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Only display IDs\"\n        },\n        {\n          \"name\": \"services\",\n          \"type\": \"bool\",\n          \"description\": \"Display services\"\n        },\n        {\n          \"name\": \"status\",\n          \"type\": \"stringArray\",\n          \"description\": \"Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]\"\n        }\n      ]\n    },\n    {\n      \"name\": \"publish\",\n      \"usage\": \"docker compose publish [OPTIONS] REPOSITORY[:TAG]\",\n      \"description\": \"Publish compose application\",\n      \"flags\": [\n        {\n          \"name\": \"app\",\n          \"type\": \"bool\",\n          \"description\": \"Published compose application (includes referenced images)\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"oci-version\",\n          \"type\": \"string\",\n          \"description\": \"OCI image/artifact specification version (automatically determined by default)\"\n        },\n        {\n          \"name\": \"resolve-image-digests\",\n          \"type\": \"bool\",\n          \"description\": \"Pin image tags to digests\"\n        },\n        {\n          \"name\": \"with-env\",\n          \"type\": \"bool\",\n          \"description\": \"Include environment variables in the published OCI artifact\"\n        },\n        {\n          \"name\": \"yes\",\n          \"shorthand\": \"y\",\n          \"type\": \"bool\",\n          \"description\": \"Assume \\\"yes\\\" as answer to all prompts\"\n        }\n      ]\n    },\n    {\n      \"name\": \"pull\",\n      \"usage\": \"docker compose pull [OPTIONS] [SERVICE...]\",\n      \"description\": \"Pull service images\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"ignore-buildable\",\n          \"type\": \"bool\",\n          \"description\": \"Ignore images that can be built\"\n        },\n        {\n          \"name\": \"ignore-pull-failures\",\n          \"type\": \"bool\",\n          \"description\": \"Pull what it can and ignores images with pull failures\"\n        },\n        {\n          \"name\": \"include-deps\",\n          \"type\": \"bool\",\n          \"description\": \"Also pull services declared as dependencies\"\n        },\n        {\n          \"name\": \"policy\",\n          \"type\": \"string\",\n          \"description\": \"Apply pull policy (\\\"missing\\\"|\\\"always\\\")\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Pull without printing progress information\"\n        }\n      ]\n    },\n    {\n      \"name\": \"push\",\n      \"usage\": \"docker compose push [OPTIONS] [SERVICE...]\",\n      \"description\": \"Push service images\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"ignore-push-failures\",\n          \"type\": \"bool\",\n          \"description\": \"Push what it can and ignores images with push failures\"\n        },\n        {\n          \"name\": \"include-deps\",\n          \"type\": \"bool\",\n          \"description\": \"Also push images of services declared as dependencies\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Push without printing progress information\"\n        }\n      ]\n    },\n    {\n      \"name\": \"restart\",\n      \"usage\": \"docker compose restart [OPTIONS] [SERVICE...]\",\n      \"description\": \"Restart service containers\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"no-deps\",\n          \"type\": \"bool\",\n          \"description\": \"Don't restart dependent services\"\n        },\n        {\n          \"name\": \"timeout\",\n          \"shorthand\": \"t\",\n          \"type\": \"int\",\n          \"description\": \"Specify a shutdown timeout in seconds\"\n        }\n      ]\n    },\n    {\n      \"name\": \"rm\",\n      \"usage\": \"docker compose rm [OPTIONS] [SERVICE...]\",\n      \"description\": \"Removes stopped service containers\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"force\",\n          \"shorthand\": \"f\",\n          \"type\": \"bool\",\n          \"description\": \"Don't ask to confirm removal\"\n        },\n        {\n          \"name\": \"stop\",\n          \"shorthand\": \"s\",\n          \"type\": \"bool\",\n          \"description\": \"Stop the containers, if required, before removing\"\n        },\n        {\n          \"name\": \"volumes\",\n          \"shorthand\": \"v\",\n          \"type\": \"bool\",\n          \"description\": \"Remove any anonymous volumes attached to containers\"\n        }\n      ]\n    },\n    {\n      \"name\": \"run\",\n      \"usage\": \"docker compose run [OPTIONS] SERVICE [COMMAND] [ARGS...]\",\n      \"description\": \"Run a one-off command on a service\",\n      \"flags\": [\n        {\n          \"name\": \"build\",\n          \"type\": \"bool\",\n          \"description\": \"Build image before starting container\"\n        },\n        {\n          \"name\": \"cap-add\",\n          \"type\": \"list\",\n          \"description\": \"Add Linux capabilities\"\n        },\n        {\n          \"name\": \"cap-drop\",\n          \"type\": \"list\",\n          \"description\": \"Drop Linux capabilities\"\n        },\n        {\n          \"name\": \"detach\",\n          \"shorthand\": \"d\",\n          \"type\": \"bool\",\n          \"description\": \"Run container in background and print container ID\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"entrypoint\",\n          \"type\": \"string\",\n          \"description\": \"Override the entrypoint of the image\"\n        },\n        {\n          \"name\": \"env\",\n          \"shorthand\": \"e\",\n          \"type\": \"stringArray\",\n          \"description\": \"Set environment variables\"\n        },\n        {\n          \"name\": \"env-from-file\",\n          \"type\": \"stringArray\",\n          \"description\": \"Set environment variables from file\"\n        },\n        {\n          \"name\": \"interactive\",\n          \"shorthand\": \"i\",\n          \"type\": \"bool\",\n          \"description\": \"Keep STDIN open even if not attached (default true)\"\n        },\n        {\n          \"name\": \"label\",\n          \"shorthand\": \"l\",\n          \"type\": \"stringArray\",\n          \"description\": \"Add or override a label\"\n        },\n        {\n          \"name\": \"name\",\n          \"type\": \"string\",\n          \"description\": \"Assign a name to the container\"\n        },\n        {\n          \"name\": \"no-TTY\",\n          \"shorthand\": \"T\",\n          \"type\": \"bool\",\n          \"description\": \"Disable pseudo-TTY allocation (default: auto-detected) (default true)\"\n        },\n        {\n          \"name\": \"no-deps\",\n          \"type\": \"bool\",\n          \"description\": \"Don't start linked services\"\n        },\n        {\n          \"name\": \"publish\",\n          \"shorthand\": \"p\",\n          \"type\": \"stringArray\",\n          \"description\": \"Publish a container's port(s) to the host\"\n        },\n        {\n          \"name\": \"pull\",\n          \"type\": \"string\",\n          \"description\": \"Pull image before running (\\\"always\\\"|\\\"missing\\\"|\\\"never\\\") (default \\\"policy\\\")\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Don't print anything to STDOUT\"\n        },\n        {\n          \"name\": \"quiet-build\",\n          \"type\": \"bool\",\n          \"description\": \"Suppress progress output from the build process\"\n        },\n        {\n          \"name\": \"quiet-pull\",\n          \"type\": \"bool\",\n          \"description\": \"Pull without printing progress information\"\n        },\n        {\n          \"name\": \"remove-orphans\",\n          \"type\": \"bool\",\n          \"description\": \"Remove containers for services not defined in the Compose file\"\n        },\n        {\n          \"name\": \"rm\",\n          \"type\": \"bool\",\n          \"description\": \"Automatically remove the container when it exits\"\n        },\n        {\n          \"name\": \"service-ports\",\n          \"shorthand\": \"P\",\n          \"type\": \"bool\",\n          \"description\": \"Run command with all service's ports enabled and mapped to the host\"\n        },\n        {\n          \"name\": \"use-aliases\",\n          \"type\": \"bool\",\n          \"description\": \"Use the service's network useAliases in the network(s) the container connects to\"\n        },\n        {\n          \"name\": \"user\",\n          \"shorthand\": \"u\",\n          \"type\": \"string\",\n          \"description\": \"Run as specified username or uid\"\n        },\n        {\n          \"name\": \"volume\",\n          \"shorthand\": \"v\",\n          \"type\": \"stringArray\",\n          \"description\": \"Bind mount a volume\"\n        },\n        {\n          \"name\": \"workdir\",\n          \"shorthand\": \"w\",\n          \"type\": \"string\",\n          \"description\": \"Working directory inside the container\"\n        }\n      ]\n    },\n    {\n      \"name\": \"scale\",\n      \"usage\": \"docker compose scale [SERVICE=REPLICAS...]\",\n      \"description\": \"Scale services\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"no-deps\",\n          \"type\": \"bool\",\n          \"description\": \"Don't start linked services\"\n        }\n      ]\n    },\n    {\n      \"name\": \"start\",\n      \"usage\": \"docker compose start [SERVICE...]\",\n      \"description\": \"Start services\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"wait\",\n          \"type\": \"bool\",\n          \"description\": \"Wait for services to be running|healthy. Implies detached mode.\"\n        },\n        {\n          \"name\": \"wait-timeout\",\n          \"type\": \"int\",\n          \"description\": \"Maximum duration in seconds to wait for the project to be running|healthy\"\n        }\n      ]\n    },\n    {\n      \"name\": \"stats\",\n      \"usage\": \"docker compose stats [OPTIONS] [SERVICE]\",\n      \"description\": \"Display a live stream of container(s) resource usage statistics\",\n      \"flags\": [\n        {\n          \"name\": \"all\",\n          \"shorthand\": \"a\",\n          \"type\": \"bool\",\n          \"description\": \"Show all containers (default shows just running)\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"format\",\n          \"type\": \"string\",\n          \"description\": \"Format output using a custom template: 'table':            Print output in table format with column headers (default) 'table TEMPLATE':   Print output in table format using the given Go template 'json':             Print in JSON format 'TEMPLATE':         Print output using the given Go template. Refer to https://docs.docker.com/engine/cli/formatting/ for more information about formatting output with templates\"\n        },\n        {\n          \"name\": \"no-stream\",\n          \"type\": \"bool\",\n          \"description\": \"Disable streaming stats and only pull the first result\"\n        },\n        {\n          \"name\": \"no-trunc\",\n          \"type\": \"bool\",\n          \"description\": \"Do not truncate output\"\n        }\n      ]\n    },\n    {\n      \"name\": \"stop\",\n      \"usage\": \"docker compose stop [OPTIONS] [SERVICE...]\",\n      \"description\": \"Stop services\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"timeout\",\n          \"shorthand\": \"t\",\n          \"type\": \"int\",\n          \"description\": \"Specify a shutdown timeout in seconds\"\n        }\n      ]\n    },\n    {\n      \"name\": \"top\",\n      \"usage\": \"docker compose top [SERVICES...]\",\n      \"description\": \"Display the running processes\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        }\n      ]\n    },\n    {\n      \"name\": \"unpause\",\n      \"usage\": \"docker compose unpause [SERVICE...]\",\n      \"description\": \"Unpause services\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        }\n      ]\n    },\n    {\n      \"name\": \"up\",\n      \"usage\": \"docker compose up [OPTIONS] [SERVICE...]\",\n      \"description\": \"Create and start containers\",\n      \"flags\": [\n        {\n          \"name\": \"abort-on-container-exit\",\n          \"type\": \"bool\",\n          \"description\": \"Stops all containers if any container was stopped. Incompatible with -d\"\n        },\n        {\n          \"name\": \"abort-on-container-failure\",\n          \"type\": \"bool\",\n          \"description\": \"Stops all containers if any container exited with failure. Incompatible with -d\"\n        },\n        {\n          \"name\": \"always-recreate-deps\",\n          \"type\": \"bool\",\n          \"description\": \"Recreate dependent containers. Incompatible with --no-recreate.\"\n        },\n        {\n          \"name\": \"attach\",\n          \"type\": \"stringArray\",\n          \"description\": \"Restrict attaching to the specified services. Incompatible with --attach-dependencies.\"\n        },\n        {\n          \"name\": \"attach-dependencies\",\n          \"type\": \"bool\",\n          \"description\": \"Automatically attach to log output of dependent services\"\n        },\n        {\n          \"name\": \"build\",\n          \"type\": \"bool\",\n          \"description\": \"Build images before starting containers\"\n        },\n        {\n          \"name\": \"detach\",\n          \"shorthand\": \"d\",\n          \"type\": \"bool\",\n          \"description\": \"Detached mode: Run containers in the background\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"exit-code-from\",\n          \"type\": \"string\",\n          \"description\": \"Return the exit code of the selected service container. Implies --abort-on-container-exit\"\n        },\n        {\n          \"name\": \"force-recreate\",\n          \"type\": \"bool\",\n          \"description\": \"Recreate containers even if their configuration and image haven't changed\"\n        },\n        {\n          \"name\": \"menu\",\n          \"type\": \"bool\",\n          \"description\": \"Enable interactive shortcuts when running attached. Incompatible with --detach. Can also be enable/disable by setting COMPOSE_MENU environment var.\"\n        },\n        {\n          \"name\": \"no-attach\",\n          \"type\": \"stringArray\",\n          \"description\": \"Do not attach (stream logs) to the specified services\"\n        },\n        {\n          \"name\": \"no-build\",\n          \"type\": \"bool\",\n          \"description\": \"Don't build an image, even if it's policy\"\n        },\n        {\n          \"name\": \"no-color\",\n          \"type\": \"bool\",\n          \"description\": \"Produce monochrome output\"\n        },\n        {\n          \"name\": \"no-deps\",\n          \"type\": \"bool\",\n          \"description\": \"Don't start linked services\"\n        },\n        {\n          \"name\": \"no-log-prefix\",\n          \"type\": \"bool\",\n          \"description\": \"Don't print prefix in logs\"\n        },\n        {\n          \"name\": \"no-recreate\",\n          \"type\": \"bool\",\n          \"description\": \"If containers already exist, don't recreate them. Incompatible with --force-recreate.\"\n        },\n        {\n          \"name\": \"no-start\",\n          \"type\": \"bool\",\n          \"description\": \"Don't start the services after creating them\"\n        },\n        {\n          \"name\": \"pull\",\n          \"type\": \"string\",\n          \"description\": \"Pull image before running (\\\"always\\\"|\\\"missing\\\"|\\\"never\\\") (default \\\"policy\\\")\"\n        },\n        {\n          \"name\": \"quiet-build\",\n          \"type\": \"bool\",\n          \"description\": \"Suppress the build output\"\n        },\n        {\n          \"name\": \"quiet-pull\",\n          \"type\": \"bool\",\n          \"description\": \"Pull without printing progress information\"\n        },\n        {\n          \"name\": \"remove-orphans\",\n          \"type\": \"bool\",\n          \"description\": \"Remove containers for services not defined in the Compose file\"\n        },\n        {\n          \"name\": \"renew-anon-volumes\",\n          \"shorthand\": \"V\",\n          \"type\": \"bool\",\n          \"description\": \"Recreate anonymous volumes instead of retrieving data from the previous containers\"\n        },\n        {\n          \"name\": \"scale\",\n          \"type\": \"scale\",\n          \"description\": \"Scale SERVICE to NUM instances. Overrides the scale setting in the Compose file if present.\"\n        },\n        {\n          \"name\": \"timeout\",\n          \"shorthand\": \"t\",\n          \"type\": \"int\",\n          \"description\": \"Use this timeout in seconds for container shutdown when attached or when containers are already running\"\n        },\n        {\n          \"name\": \"timestamps\",\n          \"type\": \"bool\",\n          \"description\": \"Show timestamps\"\n        },\n        {\n          \"name\": \"wait\",\n          \"type\": \"bool\",\n          \"description\": \"Wait for services to be running|healthy. Implies detached mode.\"\n        },\n        {\n          \"name\": \"wait-timeout\",\n          \"type\": \"int\",\n          \"description\": \"Maximum duration in seconds to wait for the project to be running|healthy\"\n        },\n        {\n          \"name\": \"watch\",\n          \"shorthand\": \"w\",\n          \"type\": \"bool\",\n          \"description\": \"Watch source code and rebuild/refresh containers when files are updated.\"\n        },\n        {\n          \"name\": \"yes\",\n          \"shorthand\": \"y\",\n          \"type\": \"bool\",\n          \"description\": \"Assume \\\"yes\\\" as answer to all prompts and run non-interactively\"\n        }\n      ]\n    },\n    {\n      \"name\": \"version\",\n      \"usage\": \"docker compose version [OPTIONS]\",\n      \"description\": \"Show the Docker Compose version information\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"format\",\n          \"shorthand\": \"f\",\n          \"type\": \"string\",\n          \"description\": \"Format the output. Values: [pretty | json]. (Default: pretty)\"\n        },\n        {\n          \"name\": \"short\",\n          \"type\": \"bool\",\n          \"description\": \"Shows only Compose's version number\"\n        }\n      ]\n    },\n    {\n      \"name\": \"volumes\",\n      \"usage\": \"docker compose volumes [OPTIONS] [SERVICE...]\",\n      \"description\": \"List volumes\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"format\",\n          \"type\": \"string\",\n          \"description\": \"Format output using a custom template: 'table':            Print output in table format with column headers (default) 'table TEMPLATE':   Print output in table format using the given Go template 'json':             Print in JSON format 'TEMPLATE':         Print output using the given Go template. Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates (default \\\"table\\\")\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"shorthand\": \"q\",\n          \"type\": \"bool\",\n          \"description\": \"Only display volume names\"\n        }\n      ]\n    },\n    {\n      \"name\": \"wait\",\n      \"usage\": \"docker compose wait SERVICE [SERVICE...] [OPTIONS]\",\n      \"description\": \"Block until containers of all (or specified) services stop.\",\n      \"flags\": [\n        {\n          \"name\": \"down-project\",\n          \"type\": \"bool\",\n          \"description\": \"Drops project when the first container stops\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        }\n      ]\n    },\n    {\n      \"name\": \"watch\",\n      \"usage\": \"docker compose watch [SERVICE...]\",\n      \"description\": \"Watch build context for service and rebuild/refresh containers when files are updated\",\n      \"flags\": [\n        {\n          \"name\": \"dry-run\",\n          \"type\": \"bool\",\n          \"description\": \"Execute command in dry run mode\"\n        },\n        {\n          \"name\": \"no-up\",\n          \"type\": \"bool\",\n          \"description\": \"Do not build \\u0026 start services before watching\"\n        },\n        {\n          \"name\": \"prune\",\n          \"type\": \"bool\",\n          \"description\": \"Prune dangling images on rebuild\",\n          \"default\": \"true\"\n        },\n        {\n          \"name\": \"quiet\",\n          \"type\": \"bool\",\n          \"description\": \"hide build output\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/compose/engagement.go",
    "content": "package compose\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\tcompose \"github.com/compose-spec/compose-go/v2/types\"\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype engagement struct {\n\tserviceExtension\n\tenvironment map[string]string\n\tdaemonID    *daemon.Identifier\n\tsftpPort    uint16\n\tdaemonIP    netip.Addr\n}\n\nfunc createEngagement(ud daemon.UserClient, e serviceExtension, sftpPort uint16) (*engagement, error) {\n\tae := &engagement{\n\t\tserviceExtension: e,\n\t\tdaemonID:         ud.DaemonID(),\n\t\tdaemonIP:         ud.DaemonInfo().ContainerIP,\n\t\tsftpPort:         sftpPort,\n\t}\n\treturn ae, nil\n}\n\nfunc (a *engagement) assignEnvAndCreateMounts(remoteEnv map[string]string, remoteMounts map[string]int32, t *transformer) {\n\tenv := make(map[string]string)\n\tmaps.Merge(env, remoteEnv)\n\ta.environment = env\n\twe, ok := a.serviceExtension.(mountsExtension)\n\tif !ok {\n\t\treturn\n\t}\n\tmounts, serviceVolumes := we.desiredRemoteMounts(types.MountPoliciesFromRPC(remoteMounts))\n\tif len(mounts) == 0 {\n\t\treturn\n\t}\n\tro := a.engagementType() == types.EngagementTypeIngest || a.engagementType() == types.EngagementTypeWiretap\n\tctx := a.connection().Context\n\tclog.Debugf(ctx, \"mounts: %v, ro %t\", mounts, ro)\n\tcreateVolumes(ctx, netip.AddrPortFrom(a.daemonIP, a.sftpPort), a.environment[\"TELEPRESENCE_CONTAINER\"], mounts, serviceVolumes, ro, t)\n}\n\nconst (\n\tconnectionAnnotationPrefix = \"telepresence.io/connection-\"\n\tmountPortAnnotation        = \"telepresence.io/mount-port\"\n)\n\nfunc (a *engagement) maybeAddConnection(s *compose.ServiceConfig) bool {\n\tconn := a.connection()\n\tcn := conn.Name\n\tif cn == \"\" {\n\t\tcn = \"default\"\n\t}\n\tmyKey := connectionAnnotationPrefix + cn\n\tfor k, sn := range s.Annotations {\n\t\tif strings.HasPrefix(k, connectionAnnotationPrefix) {\n\t\t\tif k == myKey {\n\t\t\t\t// Never add the same connection twice.\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tvar subnets []netip.Prefix\n\t\t\t_ = json.Unmarshal([]byte(sn), &subnets, true)\n\t\t\tfor _, osn := range subnets {\n\t\t\t\tfor _, msn := range conn.subnets {\n\t\t\t\t\tif osn.Overlaps(msn) {\n\t\t\t\t\t\tclog.Warnf(conn, \"Subnet %s in connection %s overlaps with subnet %s in connection %s. This prevents it from being added to service %s\",\n\t\t\t\t\t\t\tmsn, cn, osn, strings.TrimPrefix(k, connectionAnnotationPrefix), s.Name)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif s.Annotations == nil {\n\t\ts.Annotations = make(map[string]string)\n\t}\n\tmyAnn, _ := json.Marshal(conn.subnets)\n\ts.Annotations[myKey] = string(myAnn)\n\tif s.Networks == nil {\n\t\ts.Networks = make(map[string]*compose.ServiceNetworkConfig)\n\t}\n\ts.Networks[a.daemonID.Name] = nil\n\treturn true\n}\n\nfunc (a *engagement) engageService(s *compose.ServiceConfig) {\n\ta.maybeAddConnection(s)\n\tdnsIP := a.connection().dnsIP\n\tif ipS := dnsIP.String(); !slices.Contains(s.DNS, ipS) {\n\t\ts.DNS = append(s.DNS, ipS)\n\t}\n\tif !slices.Contains(s.DNSSearch, client.Tel2SubDomain) {\n\t\ts.DNSSearch = append(s.DNSSearch, client.Tel2SubDomain)\n\t}\n\tenv := a.environment\n\tif len(env) > 0 {\n\t\tif s.Environment == nil {\n\t\t\ts.Environment = make(compose.MappingWithEquals, len(env))\n\t\t}\n\t\tfor k, v := range env {\n\t\t\t// Don't overwrite the value declared in the compose-spec.\n\t\t\tif _, ok := s.Environment[k]; !ok {\n\t\t\t\ts.Environment[k] = &v\n\t\t\t}\n\t\t}\n\t}\n\tif a.sftpPort > 0 {\n\t\tif s.Annotations == nil {\n\t\t\ts.Annotations = make(map[string]string)\n\t\t}\n\t\ts.Annotations[mountPortAnnotation] = strconv.Itoa(int(a.sftpPort))\n\t}\n}\n\nfunc (a *engagement) engageProxyDependents(p *compose.Project, n string, dependents []string) {\n\tconn := a.connection()\n\tsm := p.Services\n\n\tdnsIP := a.connection().dnsIP.String()\n\tfor _, d := range dependents {\n\t\t// Dependent services need a new DNS for the proxy, and also the network\n\t\t// that routes its IP.\n\t\tds := sm[d]\n\t\tif !a.maybeAddConnection(&ds) {\n\t\t\tcontinue\n\t\t}\n\t\tif len(conn.proxies) > 0 {\n\t\t\textraHosts := make(compose.HostsList, len(conn.proxies))\n\t\t\tfor name, ip := range conn.proxies {\n\t\t\t\textraHosts[name] = []string{ip.String()}\n\t\t\t}\n\t\t\tif ds.ExtraHosts != nil {\n\t\t\t\tmaps.Merge(ds.ExtraHosts, extraHosts)\n\t\t\t} else {\n\t\t\t\tds.ExtraHosts = extraHosts\n\t\t\t}\n\t\t}\n\t\tif !slices.Contains(ds.DNS, dnsIP) {\n\t\t\tds.DNS = append(ds.DNS, dnsIP)\n\t\t}\n\t\tif !slices.Contains(ds.DNSSearch, client.Tel2SubDomain) {\n\t\t\tds.DNSSearch = append(ds.DNSSearch, client.Tel2SubDomain)\n\t\t}\n\t\t// A proxied service is simply removed from the compose-spec along with any dependents.\n\t\tdelete(ds.DependsOn, n)\n\t\tsm[d] = ds\n\t}\n}\n\nfunc (a *engagement) engageProject(p *compose.Project) {\n\tif p.Networks == nil {\n\t\tp.Networks = make(compose.Networks)\n\t}\n\tnn := a.daemonID.Name\n\tif _, ok := p.Networks[nn]; !ok {\n\t\tp.Networks[nn] = compose.NetworkConfig{\n\t\t\tName:     nn,\n\t\t\tExternal: true,\n\t\t}\n\t}\n}\n\n// createVolumes creates the VolumeConfigs necessary when mounting volumes required by when engaging a remote container.\n// The hostPort is the <daemon ip>/<sftp port> where the access to the remote sftp-server is provided.\n// The mounts are provided as a map of mount policies keyed by paths.\n// Each volume is given the name of the remote container suffixed by a dash and a sequence number, starting at 1.\n// Returns a map of VolumeConfig keyed by volume target paths.\nfunc createVolumes(\n\tctx context.Context,\n\thostPort netip.AddrPort,\n\tremoteContainer string,\n\tmounts types.MountPolicies,\n\tserviceVolumes map[string]*compose.ServiceVolumeConfig,\n\tro bool,\n\tt *transformer,\n) {\n\tvar plugin string\n\ti := 0\n\tfor dir, policy := range mounts {\n\t\tsv := serviceVolumes[dir]\n\t\tvolRO := ro || sv.ReadOnly\n\t\tswitch policy {\n\t\tcase types.MountPolicyIgnore, types.MountPolicyLocal:\n\t\t\tcontinue\n\t\tcase types.MountPolicyRemoteReadOnly:\n\t\t\tvolRO = true\n\t\tdefault:\n\t\t}\n\t\tvar err error\n\t\tif plugin == \"\" {\n\t\t\tplugin, err = docker.EnsureVolumePlugin(ctx)\n\t\t\tif err != nil {\n\t\t\t\tioutil.Printf(output.Err(ctx), \"Remote mount disabled: %s\\n\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\tt.volumes().Compute(sv.Source, func(prev *compose.VolumeConfig, loaded bool) (vol *compose.VolumeConfig, op xsync.ComputeOp) {\n\t\t\tif loaded {\n\t\t\t\tif !volRO && prev.DriverOpts[\"ro\"] == \"true\" {\n\t\t\t\t\t// A read-write volume is needed by this service, so we can't use a read-only one.\n\t\t\t\t\tdelete(prev.DriverOpts, \"ro\")\n\t\t\t\t}\n\t\t\t\treturn nil, xsync.CancelOp\n\t\t\t}\n\t\t\ti++\n\t\t\treturn createVolume(ctx, plugin, hostPort, fmt.Sprintf(\"%s-%d\", remoteContainer, i), remoteContainer, dir, volRO), xsync.UpdateOp\n\t\t})\n\t}\n}\n\nfunc createVolume(ctx context.Context, pluginName string, hostPort netip.AddrPort, volumeName, container, dir string, ro bool) *compose.VolumeConfig {\n\treturn &compose.VolumeConfig{\n\t\tName:       volumeName,\n\t\tDriver:     pluginName,\n\t\tDriverOpts: docker.VolumeDriverOpts(ctx, pluginName, hostPort, volumeName, container, dir, ro),\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/compose/extension.go",
    "content": "package compose\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\tcompose \"github.com/compose-spec/compose-go/v2/types\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/intercept\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype serviceExtension interface {\n\t// Activate the service extension.\n\tactivate(*transformer) (*engagement, error)\n\n\tdeactivate() error\n\n\tengaged() (*engagement, error)\n\n\tengagementType() types.EngagementType\n\n\t// name used for the proxy or the engagement. It defaults to the name of the compose-service.\n\tname() string\n\n\t// connection used when the extension is activated.\n\tconnection() *connection\n\n\t// ConnectionName to use for this extension. It is optional unless more than one connection is present\n\t// in the top level x-tele extension.\n\tconnectionName() string\n\n\t// ComposeService is the extended docker-compose service.\n\tcomposeService() *compose.ServiceConfig\n\n\t// NeedsVolumes returns true if the extended service has volumes unless this is a \"connect\" or \"proxy\" extension.\n\tneedsVolumes() bool\n\n\t// SetConnection assigns the connection used when the extension is activated.\n\tsetConnection(c *connection)\n\n\tinit(*config, types.EngagementType, *compose.ServiceConfig)\n}\n\ntype mountsExtension interface {\n\tserviceExtension\n\n\tdesiredRemoteMounts(remoteMounts types.MountPolicies) (types.MountPolicies, map[string]*compose.ServiceVolumeConfig)\n}\n\ntype workloadExtension interface {\n\tmountsExtension\n\n\t// workload is the name of the engaged workload. It defaults to name().\n\tworkload() string\n\n\tcreateInterceptRequest(localMountPort uint16) (*connector.CreateInterceptRequest, error)\n}\n\ntype servicePortExtension interface {\n\tserviceExtension\n\tservicePorts() []types.PortMapping\n}\n\nfunc (c *config) parseServiceExtension(composeService *compose.ServiceConfig, v any) (se serviceExtension, err error) {\n\tm, ok := v.(map[string]any)\n\tif !ok {\n\t\treturn nil, errcat.User.Newf(\"%s extension is not a map\", extensionKey)\n\t}\n\ttyp, ok := m[\"type\"].(string)\n\tif !ok {\n\t\treturn nil, errcat.User.Newf(\"%s extension must have a type\", extensionKey)\n\t}\n\tet, err := types.ParseEngagementType(typ)\n\tif err != nil {\n\t\treturn nil, errcat.User.Newf(\"%s extension has invalid type: %v\", extensionKey, err)\n\t}\n\n\tdata, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch et {\n\tcase types.EngagementTypeConnect:\n\t\tse = &extension{}\n\tcase types.EngagementTypeIngest:\n\t\tse = &ingestExtension{}\n\tcase types.EngagementTypeIntercept:\n\t\tse = &interceptExtension{}\n\tcase types.EngagementTypeProxy:\n\t\tse = &proxyExtension{}\n\tcase types.EngagementTypeReplace:\n\t\tse = &replaceExtension{}\n\tcase types.EngagementTypeWiretap:\n\t\tse = &wiretapExtension{}\n\tdefault:\n\t\treturn nil, errcat.User.Newf(\"%s has unsupported extension type %s\", extensionKey, et)\n\t}\n\n\terr = json.Unmarshal(data, se, true)\n\tif err != nil {\n\t\treturn nil, errcat.User.New(err)\n\t}\n\tse.init(c, et, composeService)\n\treturn se, nil\n}\n\ntype extension struct {\n\tType       types.EngagementType `json:\"type\"`\n\tConnection string               `json:\"connection,omitempty\"`\n\tcomposeSvc *compose.ServiceConfig\n\tconn       *connection\n}\n\nfunc (e *extension) init(_ *config, et types.EngagementType, composeService *compose.ServiceConfig) {\n\te.Type = et\n\te.composeSvc = composeService\n}\n\n// Name is the name of the service that this proxy connects to. It defaults to the name of the compose-service.\nfunc (e *extension) name() string {\n\treturn e.composeSvc.Name\n}\n\nfunc (e *extension) engagementType() types.EngagementType {\n\treturn e.Type\n}\n\nfunc (e *extension) connectionName() string {\n\treturn e.Connection\n}\n\n// ComposeService is the name of the extended docker-compose service.\nfunc (e *extension) composeService() *compose.ServiceConfig {\n\treturn e.composeSvc\n}\n\nfunc (e *extension) connection() *connection {\n\treturn e.conn\n}\n\nfunc (e *extension) engaged() (*engagement, error) {\n\treturn createEngagement(daemon.MustGetUserClient(e.conn), e, 0)\n}\n\nfunc (e *extension) needsVolumes() bool {\n\treturn false\n}\n\nfunc (e *extension) setConnection(c *connection) {\n\te.conn = c\n}\n\nfunc (e *extension) activate(*transformer) (*engagement, error) {\n\treturn createEngagement(daemon.MustGetUserClient(e.conn), e, 0)\n}\n\nfunc (e *extension) deactivate() error {\n\treturn nil\n}\n\ntype proxyExtension struct {\n\textension\n\tName  string              `json:\"name\"`\n\tPorts []types.PortMapping `json:\"ports,omitempty\"`\n}\n\nfunc (e *proxyExtension) init(c *config, et types.EngagementType, composeService *compose.ServiceConfig) {\n\te.extension.init(c, et, composeService)\n\tif e.Name == \"\" {\n\t\te.Name = composeService.Name\n\t}\n}\n\nfunc (e *proxyExtension) activate(*transformer) (*engagement, error) {\n\treturn createEngagement(daemon.MustGetUserClient(e.conn), e, 0)\n}\n\n// Name is the name of the service that this proxy connects to. It defaults to the name of the compose-service.\nfunc (e *proxyExtension) name() string {\n\treturn e.Name\n}\n\n// ServicePorts mappings from local ports to service ports.\nfunc (e *proxyExtension) servicePorts() []types.PortMapping {\n\treturn e.Ports\n}\n\ntype engageExtension struct {\n\textension\n\tName   string `json:\"name\"`\n\tmounts []volumeMountPolicy\n}\n\nfunc (e *engageExtension) init(c *config, et types.EngagementType, composeService *compose.ServiceConfig) {\n\te.extension.init(c, et, composeService)\n\tif e.Name == \"\" {\n\t\te.Name = composeService.Name\n\t}\n\te.mounts = c.Mounts\n}\n\n// Name is the name of the engagement. It defaults to the name of the compose-service.\nfunc (e *engageExtension) name() string {\n\treturn e.Name\n}\n\n// Workload is the name of the engaged workload. It defaults to Name.\nfunc (e *engageExtension) workload() string {\n\treturn e.Name\n}\n\nfunc (e *engageExtension) needsVolumes() bool {\n\treturn len(e.composeService().Volumes) > 0\n}\n\nfunc (e *engageExtension) desiredRemoteMounts(remoteMounts types.MountPolicies) (types.MountPolicies, map[string]*compose.ServiceVolumeConfig) {\n\tdesiredMounts := make(types.MountPolicies, len(remoteMounts))\n\tvolumes := make(map[string]*compose.ServiceVolumeConfig)\n\tcomposeVolumes := e.composeService().Volumes\n\tfor p, m := range remoteMounts {\n\t\tif m == types.MountPolicyRemote || m == types.MountPolicyRemoteReadOnly {\n\t\t\tfor vi := range composeVolumes {\n\t\t\t\tv := &composeVolumes[vi]\n\t\t\t\tif v.Type == compose.VolumeTypeVolume && v.Target == p {\n\t\t\t\t\t// This docker compose volume targets the remote mount point.\n\t\t\t\t\tfor _, mp := range e.mounts {\n\t\t\t\t\t\tif mp.Matches(v.Source) {\n\t\t\t\t\t\t\tm = mp.Policy\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif m == types.MountPolicyRemote || m == types.MountPolicyRemoteReadOnly {\n\t\t\t\t\t\tv.ReadOnly = v.ReadOnly || m == types.MountPolicyRemoteReadOnly\n\t\t\t\t\t\tvolumes[p] = v\n\t\t\t\t\t\tdesiredMounts[p] = m\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn desiredMounts, volumes\n}\n\ntype httpFilterExtension struct {\n\tengageExtension\n\tHttpFilters  map[string]string   `json:\"httpFilters,omitempty\"`\n\tMetadata     map[string]string   `json:\"metadata,omitempty\"`\n\tPorts        []types.PortMapping `json:\"ports,omitempty\"`\n\tPaths        []string            `json:\"httpPaths,omitempty\"`\n\tPathPrefixes []string            `json:\"httpPathPrefixes,omitempty\"`\n\tPathRegexps  []string            `json:\"httpPathRegexps,omitempty\"`\n}\n\nfunc (e *httpFilterExtension) amendInterceptSpec(spec *manager.InterceptSpec) error {\n\tports := e.servicePorts()\n\tif len(ports) == 0 {\n\t\treturn fmt.Errorf(\"a %s requires at least one port\", e.engagementType())\n\t}\n\terr := addPortsSpec(spec, ports)\n\tif err != nil {\n\t\treturn err\n\t}\n\tspec.HeaderFilters = e.HttpFilters\n\tspec.PathFilters = intercept.BuildPathFilters(e.Paths, e.PathPrefixes, e.PathRegexps)\n\tif len(spec.HeaderFilters) > 0 || len(spec.PathFilters) > 0 {\n\t\tspec.Mechanism = \"http\"\n\t}\n\tspec.Metadata = e.Metadata\n\treturn nil\n}\n\nfunc (e *httpFilterExtension) servicePorts() []types.PortMapping {\n\treturn e.Ports\n}\n\ntype interceptExtension struct {\n\thttpFilterExtension\n\tWorkload string               `json:\"workload,omitempty\"`\n\tService  string               `json:\"service,omitempty\"`\n\tToPod    []types.PortAndProto `json:\"toPod,omitempty\"`\n}\n\nfunc (e *interceptExtension) deactivate() error {\n\treturn deactivateIntercept(e)\n}\n\nfunc (e *interceptExtension) init(c *config, et types.EngagementType, composeService *compose.ServiceConfig) {\n\te.engageExtension.init(c, et, composeService)\n\tif e.Workload == \"\" {\n\t\te.Workload = e.Name\n\t}\n}\n\n// Workload is the name of the engaged workload. It defaults to Name.\nfunc (e *interceptExtension) workload() string {\n\treturn e.Workload\n}\n\nfunc (e *interceptExtension) activate(t *transformer) (*engagement, error) {\n\treturn activateIntercept(e, t)\n}\n\nfunc (e *interceptExtension) engaged() (*engagement, error) {\n\treturn createEngagement(daemon.MustGetUserClient(e.conn), e, 0)\n}\n\nfunc (e *interceptExtension) service() string {\n\treturn e.Service\n}\n\nfunc (e *interceptExtension) toPod() []types.PortAndProto {\n\treturn e.ToPod\n}\n\nfunc (e *interceptExtension) createInterceptRequest(localMountPort uint16) (*connector.CreateInterceptRequest, error) {\n\tir := createInterceptRequest(e, localMountPort)\n\tspec := ir.Spec\n\tspec.ServiceName = e.service()\n\tfor _, toPod := range e.toPod() {\n\t\tspec.LocalPorts = append(spec.LocalPorts, toPod.String())\n\t}\n\terr := e.amendInterceptSpec(spec)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ir, nil\n}\n\ntype ingestExtension struct {\n\tengageExtension\n\tContainer string               `json:\"container,omitempty\"`\n\tToPod     []types.PortAndProto `json:\"toPod,omitempty\"`\n}\n\nfunc (e *ingestExtension) activate(t *transformer) (*engagement, error) {\n\tctx := e.conn\n\tud := daemon.MustGetUserClient(ctx)\n\tsftpPort, err := t.config.getMountPort(e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// The ingest might be active already.\n\tii, err := ud.GetIngest(ctx, &connector.IngestIdentifier{WorkloadName: e.workload()})\n\tif err != nil {\n\t\tif status.Code(err) != codes.NotFound {\n\t\t\treturn nil, grpc.FromGRPC(err)\n\t\t}\n\t}\n\tif ii == nil {\n\t\tir := &connector.IngestRequest{\n\t\t\tIdentifier: &connector.IngestIdentifier{\n\t\t\t\tWorkloadName:  e.name(),\n\t\t\t\tContainerName: e.container(),\n\t\t\t},\n\t\t\tLocalMountPort: int32(sftpPort),\n\t\t}\n\t\tfor _, toPod := range e.toPod() {\n\t\t\tir.LocalPorts = append(ir.LocalPorts, toPod.String())\n\t\t}\n\t\tii, err = ud.Ingest(e.conn, ir)\n\t\tif err != nil {\n\t\t\treturn nil, grpc.FromGRPC(err)\n\t\t}\n\t}\n\tae, err := createEngagement(ud, e, sftpPort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tae.assignEnvAndCreateMounts(ii.Environment, ii.Mounts, t)\n\treturn ae, nil\n}\n\nfunc (e *ingestExtension) container() string {\n\treturn e.Container\n}\n\nfunc (e *ingestExtension) deactivate() error {\n\tctx := context.WithoutCancel(e.connection().Context)\n\tud := daemon.MustGetUserClient(ctx)\n\tig, err := ud.GetIngest(ctx, &connector.IngestIdentifier{\n\t\tWorkloadName:  e.workload(),\n\t\tContainerName: e.container(),\n\t})\n\tif err != nil {\n\t\tif status.Code(err) == codes.NotFound {\n\t\t\terr = nil\n\t\t}\n\t\treturn grpc.FromGRPC(err)\n\t}\n\t_, err = ud.LeaveIngest(ctx, &connector.IngestIdentifier{\n\t\tWorkloadName:  ig.Workload,\n\t\tContainerName: ig.Container,\n\t})\n\treturn grpc.FromGRPC(err)\n}\n\nfunc (e *ingestExtension) engaged() (*engagement, error) {\n\treturn createEngagement(daemon.MustGetUserClient(e.conn), e, 0)\n}\n\n// ToPod maps local ports to ports in an engaged pod.\nfunc (e *ingestExtension) toPod() []types.PortAndProto {\n\treturn e.ToPod\n}\n\ntype replaceExtension struct {\n\tengageExtension\n\n\t// Container is the name of the container that Telepresence will replace.\n\tContainer string `json:\"container,omitempty\"`\n\n\t// Ports maps container ports to local ports.\n\tPorts []types.PortMapping `json:\"ports,omitempty\"`\n\n\t// ToPod maps local ports to ports in an engaged pod.\n\tToPod []types.PortAndProto `json:\"toPod,omitempty\"`\n}\n\nfunc (e *replaceExtension) activate(t *transformer) (*engagement, error) {\n\treturn activateIntercept(e, t)\n}\n\nfunc (e *replaceExtension) deactivate() error {\n\treturn deactivateIntercept(e)\n}\n\nfunc (e *replaceExtension) engaged() (*engagement, error) {\n\treturn createEngagement(daemon.MustGetUserClient(e.conn), e, 0)\n}\n\nfunc (e *replaceExtension) container() string {\n\treturn e.Container\n}\n\nfunc (e *replaceExtension) containerPorts() []types.PortMapping {\n\treturn e.Ports\n}\n\nfunc (e *replaceExtension) toPod() []types.PortAndProto {\n\treturn e.ToPod\n}\n\ntype wiretapExtension struct {\n\thttpFilterExtension\n\n\t// Service is the Kubernetes service that Telepresence will engage with.\n\tService string `json:\"service,omitempty\"`\n}\n\nfunc (e *wiretapExtension) activate(t *transformer) (*engagement, error) {\n\treturn activateIntercept(e, t)\n}\n\nfunc (e *wiretapExtension) deactivate() error {\n\treturn deactivateIntercept(e)\n}\n\nfunc (e *wiretapExtension) engaged() (*engagement, error) {\n\treturn createEngagement(daemon.MustGetUserClient(e.conn), e, 0)\n}\n\nfunc (e *wiretapExtension) service() string {\n\treturn e.Service\n}\n\nfunc (e *wiretapExtension) createInterceptRequest(localMountPort uint16) (*connector.CreateInterceptRequest, error) {\n\tir := createInterceptRequest(e, localMountPort)\n\tspec := ir.Spec\n\tspec.ServiceName = e.service()\n\tspec.Wiretap = true\n\tir.MountReadOnly = true\n\terr := e.amendInterceptSpec(spec)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ir, nil\n}\n\nfunc (e *replaceExtension) createInterceptRequest(localMountPort uint16) (*connector.CreateInterceptRequest, error) {\n\tir := createInterceptRequest(e, localMountPort)\n\tspec := ir.Spec\n\tspec.ContainerName = e.container()\n\tspec.Replace = true\n\tspec.NoDefaultPort = true\n\tfor _, toPod := range e.toPod() {\n\t\tspec.LocalPorts = append(spec.LocalPorts, toPod.String())\n\t}\n\tports := e.containerPorts()\n\tif len(ports) == 0 {\n\t\tspec.PortIdentifier = \"all\"\n\t\treturn ir, nil\n\t}\n\terr := addPortsSpec(spec, ports)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ir, nil\n}\n\nfunc addPortsSpec(spec *manager.InterceptSpec, ports []types.PortMapping) error {\n\tp0 := ports[0]\n\tspec.PortIdentifier = p0.To().String()\n\tspec.TargetPort = int32(p0.FromAsNumeric().Port)\n\tfor i := 1; i < len(ports); i++ {\n\t\tpm := ports[i].String()\n\t\tif colIdx := strings.IndexByte(pm, ':'); colIdx > 0 {\n\t\t\t// An entry in the \"ports\" list puts the local port first, but it's the destination in the pod-port mapping.\n\t\t\tto := pm[:colIdx]\n\t\t\tfrom := pm[colIdx+1:]\n\t\t\tif slashIdx := strings.IndexByte(from, '/'); slashIdx > 0 {\n\t\t\t\tfrom = from[:slashIdx]\n\t\t\t\tto += from[slashIdx:]\n\t\t\t}\n\t\t\tpm = from + \":\" + to\n\t\t}\n\t\tif err := types.PortMapping(pm).Validate(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tspec.PodPorts = append(spec.PodPorts, pm)\n\t}\n\treturn nil\n}\n\nfunc createInterceptRequest(e workloadExtension, localMountPort uint16) *connector.CreateInterceptRequest {\n\tspec := &manager.InterceptSpec{\n\t\tName:       e.name(),\n\t\tMechanism:  \"tcp\",\n\t\tAgent:      e.workload(),\n\t\tTargetHost: e.composeService().Name,\n\t}\n\tir := &connector.CreateInterceptRequest{\n\t\tSpec:           spec,\n\t\tLocalMountPort: int32(localMountPort),\n\t}\n\treturn ir\n}\n\nfunc activateIntercept(e workloadExtension, t *transformer) (*engagement, error) {\n\tctx := e.connection()\n\tud := daemon.MustGetUserClient(ctx)\n\tsftpPort, err := t.config.getMountPort(e)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// The intercept might be active already.\n\tii, err := ud.GetIntercept(ctx, &manager.GetInterceptRequest{Name: e.name()})\n\tif err != nil {\n\t\tif status.Code(err) != codes.NotFound {\n\t\t\treturn nil, grpc.FromGRPC(err)\n\t\t}\n\t}\n\tif ii == nil {\n\t\tir, err := e.createInterceptRequest(sftpPort)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tii, err = ud.CreateIntercept(e.connection(), ir)\n\t\tif err = grpc.FromGRPC(err); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"connector.CreateIntercept: %w\", err)\n\t\t}\n\t}\n\tae, err := createEngagement(ud, e, sftpPort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tae.assignEnvAndCreateMounts(ii.Environment, ii.Mounts, t)\n\treturn ae, nil\n}\n\nfunc deactivateIntercept(e workloadExtension) error {\n\tctx := context.WithoutCancel(e.connection().Context)\n\tud := daemon.MustGetUserClient(ctx)\n\tic, err := ud.GetIntercept(ctx, &manager.GetInterceptRequest{Name: e.name()})\n\tif err != nil {\n\t\tif status.Code(err) == codes.NotFound {\n\t\t\terr = nil\n\t\t}\n\t\treturn grpc.FromGRPC(err)\n\t}\n\t_, err = ud.RemoveIntercept(ctx, &manager.RemoveInterceptRequest2{Name: ic.Spec.Name})\n\treturn grpc.FromGRPC(err)\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/compose/extension_test.go",
    "content": "package compose\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nfunc TestAmendInterceptSpec_MechanismSetToHTTP(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\thttpFilters       map[string]string\n\t\thttpPaths         []string\n\t\thttpPathPrefixes  []string\n\t\thttpPathRegexps   []string\n\t\texpectedMechanism string\n\t}{\n\t\t{\n\t\t\tname:              \"no filters leaves mechanism as tcp\",\n\t\t\texpectedMechanism: \"tcp\",\n\t\t},\n\t\t{\n\t\t\tname:              \"header filter switches mechanism to http\",\n\t\t\thttpFilters:       map[string]string{\"x-dev-id\": \"jdoe\"},\n\t\t\texpectedMechanism: \"http\",\n\t\t},\n\t\t{\n\t\t\tname:              \"multiple header filters switch mechanism to http\",\n\t\t\thttpFilters:       map[string]string{\"x-dev-id\": \"jdoe\", \"x-env\": \"staging\"},\n\t\t\texpectedMechanism: \"http\",\n\t\t},\n\t\t{\n\t\t\tname:              \"exact path filter switches mechanism to http\",\n\t\t\thttpPaths:         []string{\"/api/v1/users\"},\n\t\t\texpectedMechanism: \"http\",\n\t\t},\n\t\t{\n\t\t\tname:              \"path prefix filter switches mechanism to http\",\n\t\t\thttpPathPrefixes:  []string{\"/api/\"},\n\t\t\texpectedMechanism: \"http\",\n\t\t},\n\t\t{\n\t\t\tname:              \"path regex filter switches mechanism to http\",\n\t\t\thttpPathRegexps:   []string{`/api/v[0-9]+/.*`},\n\t\t\texpectedMechanism: \"http\",\n\t\t},\n\t\t{\n\t\t\tname:              \"headers and paths together switch mechanism to http\",\n\t\t\thttpFilters:       map[string]string{\"x-dev-id\": \"jdoe\"},\n\t\t\thttpPathPrefixes:  []string{\"/api/\"},\n\t\t\texpectedMechanism: \"http\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\text := &httpFilterExtension{\n\t\t\t\tHttpFilters:  tt.httpFilters,\n\t\t\t\tPaths:        tt.httpPaths,\n\t\t\t\tPathPrefixes: tt.httpPathPrefixes,\n\t\t\t\tPathRegexps:  tt.httpPathRegexps,\n\t\t\t\tPorts:        []types.PortMapping{\"3005:3005\"},\n\t\t\t}\n\n\t\t\tspec := &manager.InterceptSpec{Mechanism: \"tcp\"}\n\t\t\terr := ext.amendInterceptSpec(spec)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tt.expectedMechanism, spec.Mechanism)\n\t\t})\n\t}\n}\n\nfunc TestAmendInterceptSpec_HeaderFiltersPopulated(t *testing.T) {\n\tfilters := map[string]string{\"x-dev-id\": \"jdoe\", \"x-env\": \"staging\"}\n\text := &httpFilterExtension{\n\t\tHttpFilters: filters,\n\t\tPorts:       []types.PortMapping{\"3005:3005\"},\n\t}\n\n\tspec := &manager.InterceptSpec{Mechanism: \"tcp\"}\n\terr := ext.amendInterceptSpec(spec)\n\trequire.NoError(t, err)\n\tassert.Equal(t, filters, spec.HeaderFilters)\n}\n\nfunc TestAmendInterceptSpec_NoPorts_ReturnsError(t *testing.T) {\n\text := &httpFilterExtension{\n\t\tHttpFilters: map[string]string{\"x-dev-id\": \"jdoe\"},\n\t}\n\n\tspec := &manager.InterceptSpec{Mechanism: \"tcp\"}\n\terr := ext.amendInterceptSpec(spec)\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"requires at least one port\")\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/compose/toplevelextension.go",
    "content": "package compose\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype volumeMountPolicy struct {\n\tVolume        string            `json:\"volume,omitempty\"`\n\tVolumePattern *regexp.Regexp    `json:\"volumePattern,omitempty\"`\n\tPolicy        types.MountPolicy `json:\"policy\"`\n}\n\nfunc (v *volumeMountPolicy) Matches(volumeName string) bool {\n\treturn v.VolumePattern != nil && v.VolumePattern.MatchString(volumeName) || v.Volume == volumeName\n}\n\ntype topLevelExtension struct {\n\tConnections []*connectionConfig `json:\"connections,omitempty\"`\n\tMounts      []volumeMountPolicy `json:\"mounts,omitempty\"`\n}\n\nfunc (tl *topLevelExtension) parse(v any) error {\n\tdata, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = json.Unmarshal(data, tl, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif count := len(tl.Connections); count > 1 {\n\t\t// Assert that all connections have a name and that the names are unique.\n\t\tunique := make(map[string]struct{}, count)\n\t\tfor _, cc := range tl.Connections {\n\t\t\tif cc.Name == \"\" {\n\t\t\t\treturn fmt.Errorf(\"connection name is required when multiple connections are defined\")\n\t\t\t}\n\t\t\tif _, ok := unique[cc.Name]; ok {\n\t\t\t\treturn fmt.Errorf(\"duplicate connection name %q\", cc.Name)\n\t\t\t}\n\t\t\tunique[cc.Name] = struct{}{}\n\t\t}\n\t}\n\tfor _, m := range tl.Mounts {\n\t\tif m.VolumePattern != nil {\n\t\t\tif m.Volume != \"\" {\n\t\t\t\treturn fmt.Errorf(\"volumePattern and volume are mutually exclusive\")\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/compose/transform.go",
    "content": "package compose\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"slices\"\n\t\"strings\"\n\n\tcompose \"github.com/compose-spec/compose-go/v2/types\"\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype transformer struct {\n\tconfig      *config\n\tproject     *compose.Project\n\textensions  map[string]serviceExtension\n\tengagements map[string]*engagement\n\ttpVolumes   *xsync.Map[string, *compose.VolumeConfig]\n\tselectsAll  bool\n}\n\nfunc newTransformer(config *config, p *compose.Project) (tr *transformer, err error) {\n\tt := &transformer{config: config, project: p, tpVolumes: xsync.NewMap[string, *compose.VolumeConfig](), selectsAll: true}\n\tt.extensions = make(map[string]serviceExtension)\n\tfor n, sv := range t.project.Services {\n\t\tclog.Debugf(context.Background(), \"Service %q has extension %q\", n, sv.Extensions)\n\t\tex, ok := sv.Extensions[extensionKey]\n\t\tif ok {\n\t\t\tif len(config.services) > 0 && !slices.Contains(config.services, n) {\n\t\t\t\tt.selectsAll = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\teg, err := t.config.parseServiceExtension(&sv, ex)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tt.extensions[n] = eg\n\t\t}\n\t}\n\tt.engagements = make(map[string]*engagement, len(t.extensions))\n\treturn t, nil\n}\n\nfunc (t *transformer) addEngagement(engagement *engagement) {\n\tt.engagements[engagement.composeService().Name] = engagement\n}\n\nfunc (t *transformer) createProject(cmdName, composeFile string) ([]string, error) {\n\tc := t.config\n\topts := make([]string, 0, 10)\n\topts = append(opts, \"compose\", \"--file\", composeFile)\n\n\t// Add \"compose\" specific options\n\tif c.projectName != \"\" {\n\t\topts = append(opts, \"--project-name\", c.projectName)\n\t}\n\tif c.projectDir == \"\" {\n\t\tvar err error\n\t\tc.projectDir, err = os.Getwd()\n\t\tif err != nil {\n\t\t\treturn nil, errcat.NoDaemonLogs.New(err)\n\t\t}\n\t}\n\topts = append(opts, \"--project-directory\", c.projectDir)\n\tif c.progress != \"auto\" {\n\t\topts = append(opts, \"--progress\", c.progress)\n\t}\n\tfor _, envFile := range c.envFiles {\n\t\topts = append(opts, \"--env-file\", envFile)\n\t}\n\topts = append(opts, cmdName)\n\topts = c.appendFlags(c.subCommandFlags, opts)\n\treturn opts, nil\n}\n\nfunc (t *transformer) marshalYAML() ([]byte, error) {\n\treturn t.project.MarshalYAML()\n}\n\nfunc (t *transformer) engage(ctx context.Context, e serviceExtension, aesCh chan<- *engagement) (err error) {\n\tcn := e.composeService().Name\n\tctx = progress.WithEventId(ctx, cn)\n\tprogress.Workingf(ctx, fmt.Sprintf(\"%s %s\", e.engagementType().Working(), cn))\n\tvar ae *engagement\n\tif t.config.mustBeConnected {\n\t\tae, err = e.engaged()\n\t} else {\n\t\tae, err = e.activate(t)\n\t}\n\tif err != nil {\n\t\treturn progress.MaybeWriteError(ctx, err)\n\t}\n\tprogress.Donef(ctx, fmt.Sprintf(\"%s %s\", e.engagementType().WorkDone(), cn))\n\taesCh <- ae\n\treturn nil\n}\n\nfunc (t *transformer) disengage(ctx context.Context) {\n\tprogress.Start(ctx, \"Disengaging\")\n\tfor _, n := range maps.SortedKeys(t.engagements) {\n\t\te := t.engagements[n]\n\t\teCtx := progress.WithEventId(ctx, n)\n\t\tprogress.Workingf(eCtx, fmt.Sprintf(\"%s %s\", e.engagementType().Leaving(), n))\n\t\terr := e.deactivate()\n\t\tif err != nil {\n\t\t\tclog.Error(eCtx, err)\n\t\t}\n\t\tprogress.Donef(eCtx, fmt.Sprintf(\"%s %s\", e.engagementType().Left(), n))\n\t}\n\tprogress.Stop(ctx)\n}\n\nfunc (t *transformer) runCommand(ctx context.Context, name string) error {\n\tforceRecreate := false\n\tcanCreate := t.selectsAll\n\tif canCreate {\n\t\tif f := t.config.subCommandFlags.Lookup(\"force-recreate\"); f != nil && f.Changed {\n\t\t\tforceRecreate = f.Value.String() == \"true\"\n\t\t}\n\t\tif f := t.config.subCommandFlags.Lookup(\"no-recreate\"); f != nil && f.Changed {\n\t\t\tcanCreate = f.Value.String() != \"true\"\n\t\t}\n\t}\n\tcomposeFile, err := t.createConfigFile(ctx, canCreate, forceRecreate)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif ep := t.config.existingProject; ep != nil && !t.selectsAll {\n\t\t// Verify that all extended services that provide volumes are included.\n\t\tteleVols := make(map[string]struct{})\n\t\tfor n, v := range ep.Volumes {\n\t\t\tif strings.Contains(v.Driver, \"/telemount:\") {\n\t\t\t\tteleVols[n] = struct{}{}\n\t\t\t}\n\t\t}\n\t\tclog.Debugf(ctx, \"teleVols: %v\", teleVols)\n\t\tfor n, sv := range ep.Services {\n\t\t\tex, ok := sv.Extensions[extensionKey]\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif slices.Contains(t.config.services, n) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\teg, err := t.config.parseServiceExtension(&sv, ex)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tswitch eg.engagementType() {\n\t\t\tcase types.EngagementTypeConnect, types.EngagementTypeProxy:\n\t\t\t\tcontinue\n\t\t\tdefault:\n\t\t\t}\n\t\t\tclog.Debugf(ctx, \"Checking if service %q is a volume provider\", n)\n\t\t\tif sv.Volumes != nil {\n\t\t\t\tfor _, v := range sv.Volumes {\n\t\t\t\t\tif v.Type == compose.VolumeTypeVolume {\n\t\t\t\t\t\tif _, ok := teleVols[v.Source]; ok {\n\t\t\t\t\t\t\treturn errcat.User.Newf(\"volume %q is provided extended service %q, but that service is not included\", v.Source, n)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\topts, err := t.createProject(name, composeFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = t.runCompose(ctx, name, composeFile, opts)\n\tif err != nil {\n\t\t// Prefer error output from the command to the exit code error.\n\t\terr = errcat.Silent.New(err)\n\t}\n\treturn err\n}\n\nfunc (t *transformer) runCompose(ctx context.Context, name, composeFile string, opts []string) (err error) {\n\tif name == \"up\" && !flags.HasOption(\"detach\", 'd', opts) {\n\t\treturn t.runAttachedUp(ctx, composeFile, opts)\n\t}\n\tcmd := proc.StdCommand(ctx, docker.Exe, append(opts, t.config.services...)...)\n\tcmd.Stdin = dos.Stdin(ctx)\n\tcmd.Env = os.Environ()\n\treturn cmd.Run()\n}\n\nfunc (t *transformer) runAttachedUp(parentCtx context.Context, composeFile string, opts []string) (err error) {\n\t// We need to ensure that containers are stopped when the parentCtx is canceled, but we don't want to do that\n\t// by killing the \"docker compose up\" process. There are multiple reasons for this:\n\t//\n\t// 1. If the \"docker compose up\" process is interrupted, it will detach, and the containers will continue to run\n\t//    for a while longer. We don't want that because some of them might depend on engagements that will end once\n\t//    this function returns.\n\t// 2. On windows, the \"docker compose up\" will detach, but it won't stop the containers at all.s\n\tctx := context.WithoutCancel(parentCtx)\n\tcmd := proc.StdCommand(ctx, docker.Exe, append(opts, t.config.services...)...)\n\tcmd.Stdin = dos.Stdin(ctx)\n\tcmd.Env = os.Environ()\n\terr = cmd.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tparentCtx, parentCancel := context.WithCancel(parentCtx)\n\tstopDone := make(chan struct{})\n\tgo func() {\n\t\t<-parentCtx.Done()\n\t\targs := append([]string{\"compose\", \"--file\", composeFile, \"stop\"}, t.config.services...)\n\t\tstopCmd := exec.CommandContext(ctx, docker.Exe, args...)\n\t\t// Don't assign stdout/stderr. Avoid duplicated output from \"compose up\" and \"compose stop\".\n\t\tstopCmd.Env = os.Environ()\n\t\tclog.Debug(ctx, shellquote.ShellString(docker.Exe, args))\n\t\t_ = stopCmd.Run()\n\t\tclose(stopDone)\n\t}()\n\terr = cmd.Wait()\n\tparentCancel()\n\t<-stopDone\n\treturn err\n}\n\nfunc (t *transformer) serviceExtensions() (ses map[string]serviceExtension) {\n\treturn t.extensions\n}\n\nfunc (t *transformer) volumes() *xsync.Map[string, *compose.VolumeConfig] {\n\treturn t.tpVolumes\n}\n\nfunc (t *transformer) withProfiles(profiles []string) error {\n\tp, err := t.project.WithProfiles(profiles)\n\tif err == nil {\n\t\tt.project = p\n\t}\n\treturn err\n}\n\n// ApplyEngagements ensures that the compose-spec is modified in accordance with the engagements.\nfunc (t *transformer) applyEngagements() error {\n\tif len(t.engagements) == 0 {\n\t\treturn nil\n\t}\n\n\t// WithServiceDisabled performs a deepCopy of the project when called without arguments. We\n\t// want the original Project intact.\n\tp := t.project.WithServicesDisabled()\n\tsm := p.Services\n\tdeps := make(map[string][]string)\n\tfor n, e := range t.engagements {\n\t\tif s, ok := sm[n]; ok {\n\t\t\tif e.engagementType() == types.EngagementTypeProxy {\n\t\t\t\tdeps[n] = s.GetDependents(p)\n\t\t\t\tdelete(sm, n)\n\t\t\t} else {\n\t\t\t\te.engageService(&s)\n\t\t\t\tsm[n] = s\n\t\t\t}\n\t\t}\n\t}\n\n\tfor n, e := range t.engagements {\n\t\tif proxyDeps, ok := deps[n]; ok {\n\t\t\te.engageProxyDependents(p, n, proxyDeps)\n\t\t}\n\t}\n\n\tfor _, e := range t.engagements {\n\t\te.engageProject(p)\n\t}\n\n\tif t.tpVolumes != nil {\n\t\tt.tpVolumes.Range(func(k string, v *compose.VolumeConfig) bool {\n\t\t\tp.Volumes[k] = *v\n\t\t\treturn true\n\t\t})\n\t}\n\terr := t.ensureTopLevelExtension(p)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = p.CheckContainerNameUnicity()\n\tif err == nil {\n\t\tt.project = p\n\t}\n\treturn err\n}\n\nfunc (t *transformer) ensureTopLevelExtension(p *compose.Project) error {\n\tif _, ok := p.Extensions[extensionKey]; ok {\n\t\treturn nil\n\t}\n\t// Marshal the extension to JSON and then unmarshal it back to a map. This is necessary because the\n\t// extension is a map[string]any and the marshaler used by Docker Compose doesn't support our json-tags.\n\tjs, err := json.Marshal(t.config.topLevelExtension)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar ms map[string]any\n\terr = json.Unmarshal(js, &ms, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif p.Extensions == nil {\n\t\tp.Extensions = make(map[string]any)\n\t}\n\tp.Extensions[extensionKey] = ms\n\treturn nil\n}\n\nfunc (t *transformer) connections() []*connection {\n\tccs := t.config.Connections\n\tcs := make([]*connection, 0, len(ccs))\nnextCfg:\n\tfor _, cc := range ccs {\n\t\tfor _, e := range t.engagements {\n\t\t\tif e.connection().connectionConfig == cc {\n\t\t\t\tcs = append(cs, e.connection())\n\t\t\t\tcontinue nextCfg\n\t\t\t}\n\t\t}\n\t}\n\treturn cs\n}\n\nfunc (t *transformer) createConfigFile(ctx context.Context, canCreate, forceRecreate bool) (composeFile string, err error) {\n\tcs := t.connections()\n\tfor _, c := range cs {\n\t\tud := daemon.MustGetUserClient(c)\n\t\tcomposeFile = ud.DaemonInfo().ComposeFile\n\t\tif composeFile != \"\" {\n\t\t\tif !forceRecreate {\n\t\t\t\treturn composeFile, nil\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\tif !canCreate {\n\t\treturn \"\", errcat.User.New(`the initial invocation of \"compose up\" or \"compose create\" must include all extended services`)\n\t}\n\n\tif composeFile != \"\" {\n\t\tclog.Debugf(ctx, \"Recreating existing compose file %q\", composeFile)\n\t}\n\n\terr = t.applyEngagements()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tyml, err := t.marshalYAML()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tclog.Debug(ctx, string(yml))\n\n\tif composeFile == \"\" {\n\t\tvar mcf *os.File\n\t\tmcf, err = os.CreateTemp(\"\", \"tpc-*.yaml\")\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tcomposeFile = mcf.Name()\n\t\tdefer func() {\n\t\t\tif err != nil {\n\t\t\t\t_ = os.Remove(composeFile)\n\t\t\t}\n\t\t}()\n\t\t_, err = mcf.Write(yml)\n\t\tmcf.Close()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\t// Save the name of the docker compose file in the daemon info. This ensures that a `docker compose down` is\n\t\t// issued if one of the connections is removed. This is necessary because the network represented by the\n\t\t// daemon will no longer be available.\n\t\tfor _, c := range cs {\n\t\t\tud := daemon.MustGetUserClient(c)\n\t\t\tinfo := ud.DaemonInfo()\n\t\t\tinfo.ComposeFile = composeFile\n\t\t\terr = daemon.NewUserInfoLoader(ctx).SaveInfo(info, ud.DaemonID().InfoFileName())\n\t\t}\n\t} else {\n\t\terr = os.WriteFile(composeFile, yml, 0o644)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\treturn composeFile, nil\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/flags.go",
    "content": "package docker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n)\n\ntype Flags struct {\n\tRun          bool     // --docker-run\n\tDebug        bool     // set if --docker-debug was used\n\tBuildOptions []string // --docker-build-opt key=value, // Optional flag to docker build can be repeated (but not comma separated)\n\tContext      string   // Set to build or debug by Validate function\n\tImage        string\n\tMount        string // --docker-mount // where to mount in a docker container. Defaults to mount unless mount is \"true\" or \"false\".\n\tbuild        string // --docker-build DIR | URL\n\tdebug        string // --docker-debug DIR | URL\n\targs         []string\n\timageIndex   int\n}\n\nfunc (f *Flags) AddFlags(flagSet *pflag.FlagSet, what string) {\n\tflagSet.BoolVar(&f.Run, \"docker-run\", false, fmt.Sprintf(\n\t\t`Run a Docker container with %s environment, volume mount, by passing arguments after -- to 'docker run', `+\n\t\t\t`e.g. '--docker-run -- -it --rm ubuntu:20.04 /bin/bash'`, what))\n\n\tflagSet.StringVar(&f.build, \"docker-build\", \"\", fmt.Sprintf(\n\t\t`Build a Docker container from the given docker-context (path or URL), and run it with %s environment and volume mounts, `+\n\t\t\t`by passing arguments after -- to 'docker run', e.g. '--docker-build /path/to/docker/context -- -it IMAGE /bin/bash'`, what))\n\n\tflagSet.StringVar(&f.debug, \"docker-debug\", \"\", ``+\n\t\t`Like --docker-build, but allows a debugger to run inside the container with relaxed security`)\n\n\tflagSet.StringArrayVar(&f.BuildOptions, \"docker-build-opt\", nil,\n\t\t`Options to docker-build in the form key=value, e.g. --docker-build-opt tag=mytag.`)\n\n\tflagSet.StringVar(&f.Mount, \"docker-mount\", \"\", ``+\n\t\t`The volume mount point in docker. Defaults to same as \"--mount\"`)\n}\n\nfunc (f *Flags) Validate(args []string) error {\n\tdrCount := 0\n\tif f.Run {\n\t\tdrCount++\n\t}\n\tif f.build != \"\" {\n\t\tdrCount++\n\t\tf.Context = f.build\n\t}\n\tif f.debug != \"\" {\n\t\tdrCount++\n\t\tf.Context = f.debug\n\t\tf.Debug = true\n\t}\n\talts := \"--docker-run, --docker-build, or --docker-debug\"\n\tif drCount > 1 {\n\t\treturn errcat.User.Newf(\"only one of %s can be used\", alts)\n\t}\n\tf.Run = drCount == 1\n\tif !f.Run {\n\t\tif f.Mount != \"\" {\n\t\t\treturn errcat.User.Newf(\"--docker-mount must be used together with %s\", alts)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif flags.HasOption(\"detach\", 'd', args) {\n\t\treturn errcat.User.New(\"running docker container in background using -d or --detach is not supported\")\n\t}\n\tf.Image, f.imageIndex = firstArg(args)\n\tf.args = args\n\n\t// Ensure that the image is ready to run before we create the intercept.\n\tif f.Context == \"\" {\n\t\tif f.imageIndex < 0 {\n\t\t\treturn errcat.User.New(`unable to find the image name. When using --docker-run, the syntax after \"--\" must be [OPTIONS] IMAGE [COMMAND] [ARG...]`)\n\t\t}\n\t\tif f.Image != \"IMAGE\" {\n\t\t\treturn nil\n\t\t}\n\t}\n\tf.Image = \"\"\n\tif f.imageIndex < 0 && len(args) > 0 {\n\t\treturn errcat.User.New(`` +\n\t\t\t`the string \"IMAGE\", acting as a placeholder for image ID, must be included after \"--\" when using \"--docker-build\", so ` +\n\t\t\t`that flags intended for docker run can be distinguished from the command and arguments intended for the container.`)\n\t}\n\treturn nil\n}\n\n// PullOrBuildImage will pull or build the image and return the args list suitable\n// when starting it.\nfunc (f *Flags) PullOrBuildImage(ctx context.Context) error {\n\tif f.Image != \"\" {\n\t\treturn docker.PullImage(ctx, f.Image)\n\t}\n\topts := make([]string, len(f.BuildOptions))\n\tfor i, opt := range f.BuildOptions {\n\t\topts[i] = \"--\" + opt\n\t}\n\tprogress.Working(ctx, \"Building\")\n\timageID, err := docker.BuildImage(ctx, f.Context, opts)\n\tif err != nil {\n\t\treturn progress.MaybeWriteError(ctx, err)\n\t}\n\tprogress.Done(ctx, \"Built\")\n\tif f.imageIndex < 0 {\n\t\tf.args = []string{imageID}\n\t\tf.imageIndex = 0\n\t} else {\n\t\tf.args[f.imageIndex] = imageID\n\t}\n\treturn nil\n}\n\nfunc (f *Flags) GetContainerNameAndArgs(defaultContainerName string) (string, []string, error) {\n\tname, found, err := flags.GetUnparsedValue(\"name\", 0, false, f.args)\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tif !found {\n\t\tname = defaultContainerName\n\t\tf.args = append([]string{\"--name\", name}, f.args...)\n\t\tf.imageIndex += 2\n\t}\n\treturn name, f.args, nil\n}\n\nfunc (f *Flags) AdjustImageIndex(adjust int) {\n\tif f.imageIndex >= 0 {\n\t\tf.imageIndex += adjust\n\t}\n}\n\nvar boolFlags = map[string]bool{ //nolint:gochecknoglobals // this is a constant\n\t\"--detach\":           true,\n\t\"--init\":             true,\n\t\"--interactive\":      true,\n\t\"--no-healthcheck\":   true,\n\t\"--oom-kill-disable\": true,\n\t\"--privileged\":       true,\n\t\"--publish-all\":      true,\n\t\"--quiet\":            true,\n\t\"--read-only\":        true,\n\t\"--rm\":               true,\n\t\"--sig-proxy\":        true,\n\t\"--tty\":              true,\n}\n\n// firstArg returns the first argument that isn't an option. This requires knowledge\n// about boolean docker flags, and if new such flags arrive and are used, this\n// function might return an incorrect image.\nfunc firstArg(args []string) (string, int) {\n\tt := len(args)\n\tfor i := 0; i < t; i++ {\n\t\targ := args[i]\n\t\tif !strings.HasPrefix(arg, \"-\") {\n\t\t\treturn arg, i\n\t\t}\n\t\tif strings.IndexByte(arg, '=') > 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif strings.HasPrefix(arg, \"--\") {\n\t\t\tif !boolFlags[arg] {\n\t\t\t\ti++\n\t\t\t}\n\t\t} else if strings.ContainsAny(arg, \"ehlmpuvw\") {\n\t\t\t// Shorthand flag that require an argument. Might be prefixed by shorthand booleans, e.g. -itl <label>\n\t\t\ti++\n\t\t}\n\t}\n\treturn \"\", -1\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/published_ports.go",
    "content": "package docker\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype PublishedPort struct {\n\tHostAddrPort  netip.AddrPort\n\tProtocol      string\n\tContainerPort uint16\n}\n\nfunc parsePort(s string) (uint16, error) {\n\tpn, err := strconv.ParseUint(s, 10, 16)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"%q is not a valid port number\", s)\n\t}\n\treturn uint16(pn), nil\n}\n\nfunc parsePublishedPort(pp string) (PublishedPort, error) {\n\tpc := PublishedPort{}\n\tmapping, proto, found := strings.Cut(pp, \"/\")\n\tif !found {\n\t\tpc.Protocol = \"tcp\"\n\t} else {\n\t\tpc.Protocol = strings.ToLower(proto)\n\t\tif pc.Protocol != \"tcp\" && pc.Protocol != \"udp\" {\n\t\t\treturn PublishedPort{}, fmt.Errorf(\"%q is not a valid protocol\", proto)\n\t\t}\n\t}\n\n\tvar hostPort uint16\n\tif lastColon := strings.LastIndexByte(mapping, ':'); lastColon >= 0 {\n\t\tp := mapping[lastColon+1:]\n\t\tvar err error\n\t\tpc.ContainerPort, err = parsePort(p)\n\t\tif err != nil {\n\t\t\treturn PublishedPort{}, err\n\t\t}\n\n\t\tmapping = mapping[:lastColon]\n\t\tif strings.ContainsRune(mapping, ':') {\n\t\t\tpc.HostAddrPort, err = netip.ParseAddrPort(mapping)\n\t\t\tif err != nil {\n\t\t\t\treturn PublishedPort{}, err\n\t\t\t}\n\t\t\treturn pc, nil\n\t\t}\n\t\thostPort, err = parsePort(mapping)\n\t\tif err != nil {\n\t\t\treturn PublishedPort{}, err\n\t\t}\n\t}\n\tpc.HostAddrPort = netip.AddrPortFrom(netip.IPv4Unspecified(), hostPort)\n\treturn pc, nil\n}\n\nfunc writePort(sb *strings.Builder, port uint16) {\n\tsb.WriteString(strconv.FormatUint(uint64(port), 10))\n}\n\nfunc (c PublishedPort) writeTo(sb *strings.Builder) {\n\tif !c.HostAddrPort.Addr().IsUnspecified() {\n\t\tsb.WriteString(c.HostAddrPort.String())\n\t\tsb.WriteByte(':')\n\t} else if c.HostAddrPort.Port() != 0 {\n\t\twritePort(sb, c.HostAddrPort.Port())\n\t\tsb.WriteByte(':')\n\t}\n\twritePort(sb, c.ContainerPort)\n\tif c.Protocol != \"tcp\" {\n\t\tsb.WriteByte('/')\n\t\tsb.WriteString(c.Protocol)\n\t}\n}\n\nfunc (c PublishedPort) String() string {\n\tsb := strings.Builder{}\n\tc.writeTo(&sb)\n\treturn sb.String()\n}\n\ntype PublishedPorts []PublishedPort\n\nfunc (p *PublishedPorts) String() string {\n\tsb := strings.Builder{}\n\tsb.WriteByte('[')\n\tfor i, config := range *p {\n\t\tif i > 0 {\n\t\t\tsb.WriteByte(',')\n\t\t}\n\t\tconfig.writeTo(&sb)\n\t\tsb.WriteString(config.String())\n\t}\n\tsb.WriteByte(']')\n\treturn sb.String()\n}\n\nfunc (p *PublishedPorts) Set(s string) error {\n\treturn p.Append(s)\n}\n\nfunc (p *PublishedPorts) Type() string {\n\treturn \"list\"\n}\n\nfunc (p *PublishedPorts) Append(s string) error {\n\tc, err := parsePublishedPort(s)\n\tif err == nil {\n\t\t*p = append(*p, c)\n\t}\n\treturn err\n}\n\nfunc (p *PublishedPorts) Replace(vals []string) error {\n\tpcs := make([]PublishedPort, len(vals))\n\tfor i, val := range vals {\n\t\tpc, err := parsePublishedPort(val)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpcs[i] = pc\n\t}\n\t*p = pcs\n\treturn nil\n}\n\nfunc (p *PublishedPorts) GetSlice() []string {\n\tvals := make([]string, len(*p))\n\tfor i, pc := range *p {\n\t\tvals[i] = pc.String()\n\t}\n\treturn vals\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/run_flags.go",
    "content": "package docker\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n)\n\ntype Volume struct {\n\tName    string\n\tTarget  string\n\tOptions string\n}\n\nfunc (v *Volume) String() string {\n\tn := v.Name\n\tif n == \"\" {\n\t\tn = v.Target\n\t} else {\n\t\tn += \":\" + v.Target\n\t}\n\tif v.Options != \"\" {\n\t\tn += \":\" + v.Options\n\t}\n\treturn n\n}\n\ntype Mount struct {\n\tType    string\n\tSource  string\n\tTarget  string\n\tOptions string\n}\n\ntype Network struct {\n\tName    string\n\tAliases []string\n}\n\nfunc (m *Mount) String() string {\n\tsb := new(strings.Builder)\n\tsb.WriteString(\"type=\")\n\tsb.WriteString(m.Type)\n\tif m.Source != \"\" {\n\t\tsb.WriteString(\",src=\")\n\t\tsb.WriteString(m.Source)\n\t}\n\tif m.Target != \"\" {\n\t\tsb.WriteString(\",dst=\")\n\t\tsb.WriteString(m.Target)\n\t}\n\tif m.Options != \"\" {\n\t\tsb.WriteByte(',')\n\t\tsb.WriteString(m.Options)\n\t}\n\treturn sb.String()\n}\n\ntype RunFlags struct {\n\tName    string\n\tVolumes []Volume\n\tMounts  []Mount\n}\n\nfunc ParseRunFlags(args []string) (*RunFlags, []string, error) {\n\tf := RunFlags{}\n\tvalues, err := flags.GetUnparsedValues(\"volume\", 'v', args)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tfor _, av := range values {\n\t\tvx := strings.Split(av, \":\")\n\t\tv := Volume{}\n\t\tswitch len(vx) {\n\t\tcase 1:\n\t\t\tv.Target = vx[0]\n\t\tcase 2:\n\t\t\tv.Name = vx[0]\n\t\t\tv.Target = vx[1]\n\t\tcase 3:\n\t\t\tv.Name = vx[0]\n\t\t\tv.Target = vx[1]\n\t\t\tv.Options = vx[2]\n\t\tdefault:\n\t\t\treturn nil, nil, fmt.Errorf(\"invalid volume format: %s\", av)\n\t\t}\n\t\tf.Volumes = append(f.Volumes, v)\n\t}\n\tvalues, err = flags.GetUnparsedValues(\"mount\", 0, args)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tfor _, av := range values {\n\t\tm := Mount{}\n\t\tfor _, vx := range strings.Split(av, \",\") {\n\t\t\tkv := strings.Split(vx, \"=\")\n\t\t\tif len(kv) != 2 {\n\t\t\t\treturn nil, nil, fmt.Errorf(\"invalid mount format: %s\", av)\n\t\t\t}\n\t\t\tkey := kv[0]\n\t\t\tval := kv[1]\n\t\t\tswitch key {\n\t\t\tcase \"type\":\n\t\t\t\tm.Type = val\n\t\t\tcase \"src\", \"source\":\n\t\t\t\tm.Source = val\n\t\t\tcase \"destination\", \"dst\", \"target\":\n\t\t\t\tm.Target = val\n\t\t\tdefault:\n\t\t\t\tif len(m.Options) > 0 {\n\t\t\t\t\tm.Options += \",\"\n\t\t\t\t}\n\t\t\t\tm.Options += vx\n\t\t\t}\n\t\t}\n\t\tf.Mounts = append(f.Mounts, m)\n\t}\n\tf.Name, _, err = flags.GetUnparsedValue(\"name\", 0, false, args)\n\treturn &f, args, err\n}\n"
  },
  {
    "path": "pkg/client/cli/docker/runner.go",
    "content": "package docker\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"maps\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/exec\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"slices\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/containerd/errdefs\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/env\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/mount\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype Runner struct {\n\tFlags\n\tContainerName string\n\tEnvironment   map[string]string\n\tMount         *mount.Info\n\tlocalMountDir string\n}\n\nfunc (s *Runner) Run(ctx context.Context, waitMessage string, args ...string) error {\n\tvar runFlags *RunFlags\n\tif s.imageIndex > 0 {\n\t\t// arguments between the \"--\" separator and the image name are docker run flags, and\n\t\t// we must extract the relevant network flags.\n\t\trunArgs := args[:s.imageIndex]\n\t\targs = args[s.imageIndex:]\n\t\tvar err error\n\t\torigRunArgs := slices.Clone(runArgs)\n\t\trunFlags, runArgs, err = ParseRunFlags(runArgs)\n\t\tif err != nil {\n\t\t\tclog.Debugf(ctx, \"error parsing run-flags %v: %v\", origRunArgs, err)\n\t\t\treturn err\n\t\t}\n\t\ts.imageIndex = len(runArgs)\n\t\tif len(runArgs) > 0 {\n\t\t\targs = append(runArgs, args...)\n\t\t}\n\t}\n\n\tfile, err := os.CreateTemp(\"\", \"tel-*.env\")\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create temporary environment file. %w\", err)\n\t}\n\tdefer func() {\n\t\tif err := os.Remove(file.Name()); err != nil {\n\t\t\tclog.Errorf(ctx, \"failed to remove temporary environment file %q: %v\", file.Name(), err)\n\t\t}\n\t\tif s.localMountDir != \"\" {\n\t\t\tif err := os.RemoveAll(s.localMountDir); err != nil {\n\t\t\t\tclog.Errorf(ctx, \"failed to remove local mount directory %q: %v\", s.localMountDir, err)\n\t\t\t}\n\t\t}\n\t}()\n\n\tif err = env.SyntaxDocker.WriteToFileAndClose(file, s.Environment); err != nil {\n\t\treturn err\n\t}\n\tenvFile := file.Name()\n\n\tud := daemon.MustGetUserClient(ctx)\n\n\t// Ensure that the intercept handler is stopped properly if the daemon quits\n\tprocCtx, cancel := context.WithCancel(ctx)\n\tgo func() {\n\t\tif err := daemon.NewUserInfoLoader(procCtx).CancelWhenRmFromCache(cancel, ud.DaemonID().InfoFileName()); err != nil {\n\t\t\tclog.Error(ctx)\n\t\t}\n\t}()\n\n\tprogress.Working(ctx, \"Starting\")\n\tw := s.start(procCtx, envFile, runFlags, args)\n\tif w.err == nil {\n\t\tif w.cmd == nil {\n\t\t\t// Container already exited\n\t\t\treturn nil\n\t\t}\n\t\tw.err = ud.AddHandler(ctx, s.Environment[\"TELEPRESENCE_INTERCEPT_ID\"], w.cmd, w.cni.Name)\n\t\tprogress.Done(ctx, \"Started\")\n\t} else if !errors.Is(w.err, fs.ErrNotExist) {\n\t\tw.err = progress.MaybeWriteError(ctx, w.err)\n\t}\n\n\t// Can't have the progress monitor running and show process output at the same time.\n\tprogress.Stop(ctx)\n\n\tif err = w.wait(procCtx); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (s *Runner) adjustMounts(ctx context.Context, runFlags *RunFlags, args []string) ([]string, types.MountPolicies, error) {\n\tvar mounts types.MountPolicies\n\tif m := s.Mount; m != nil {\n\t\tmounts = maps.Clone(m.Mounts)\n\t\tif runFlags != nil {\n\t\t\tif len(runFlags.Volumes) > 0 || len(runFlags.Mounts) > 0 {\n\t\t\t\tmounts = maps.Clone(mounts)\n\t\t\t\tfor _, v := range runFlags.Volumes {\n\t\t\t\t\tclog.Infof(ctx, \"Skipping auto-mounting of path %s due to user provided -v %s\", v.Target, v)\n\t\t\t\t\tdelete(mounts, v.Target)\n\t\t\t\t}\n\t\t\t\tfor _, v := range runFlags.Mounts {\n\t\t\t\t\tclog.Infof(ctx, \"Skipping auto-mounting of path %s due to user provided --mount %s\", v.Target, v)\n\t\t\t\t\tdelete(mounts, v.Target)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor path, mp := range mounts {\n\t\t\tif mp == types.MountPolicyLocal {\n\t\t\t\tif s.localMountDir == \"\" {\n\t\t\t\t\tvar err error\n\t\t\t\t\ts.localMountDir, err = os.MkdirTemp(\"\", \"telfs-local-*\")\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, nil, err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\thostPath := filepath.Join(s.localMountDir, path)\n\t\t\t\tif err := os.MkdirAll(hostPath, 0o755); err != nil {\n\t\t\t\t\tclog.Error(ctx, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tma := fmt.Sprintf(\"type=bind,src=%s,dst=%s\", hostPath, path)\n\t\t\t\tclog.Infof(ctx, \"Adding --mount %s for remote path %s, because it has a local mount policy and is not provided by user\", ma, path)\n\t\t\t\targs = append(args, \"--mount\", ma)\n\t\t\t}\n\t\t}\n\t}\n\treturn args, mounts, nil\n}\n\nfunc (s *Runner) start(ctx context.Context, envFile string, runFlags *RunFlags, args []string) *waiter {\n\tourArgs := []string{\n\t\t\"--env-file\", envFile,\n\t}\n\tw := &waiter{}\n\tw.mount = s.Mount\n\n\tif s.Debug {\n\t\tourArgs = append(ourArgs, \"--security-opt\", \"apparmor=unconfined\", \"--cap-add\", \"SYS_PTRACE\")\n\t}\n\n\t// \"--rm\" is mandatory when using --docker-run, because without it, the name cannot be reused and\n\t// the volumes cannot be removed.\n\t_, set, err := flags.GetUnparsedBoolean(args, \"rm\")\n\tif err != nil {\n\t\tw.err = err\n\t\treturn w\n\t}\n\tif !set {\n\t\tourArgs = append(ourArgs, \"--rm\")\n\t}\n\tourArgs, mounts, err := s.adjustMounts(ctx, runFlags, ourArgs)\n\tif err != nil {\n\t\tw.err = err\n\t\treturn w\n\t}\n\n\thasRemoteMounts := false\n\tud := daemon.MustGetUserClient(ctx)\n\tif !ud.Containerized() {\n\t\t// The process is containerized, but the user daemon runs on the host\n\t\tfor path, policy := range mounts {\n\t\t\tro := \"\"\n\t\t\tswitch policy {\n\t\t\tcase types.MountPolicyIgnore, types.MountPolicyLocal:\n\t\t\tcase types.MountPolicyRemoteReadOnly:\n\t\t\t\tro = \",ro\"\n\t\t\t\tfallthrough\n\t\t\tcase types.MountPolicyRemote:\n\t\t\t\thasRemoteMounts = true\n\t\t\t\tourArgs = append(ourArgs, \"--mount\", fmt.Sprintf(\"type=bind,src=%s,dst=%s%s\", filepath.Join(s.Mount.LocalDir, path), path, ro))\n\t\t\t}\n\t\t}\n\t\tourArgs = append(ourArgs, \"--dns-search\", client.Tel2SubDomain)\n\t} else {\n\t\tmaps.DeleteFunc(mounts, func(s string, policy types.MountPolicy) bool {\n\t\t\treturn policy == types.MountPolicyIgnore || policy == types.MountPolicyLocal\n\t\t})\n\t\tif len(mounts) > 0 {\n\t\t\tcontainer := s.Environment[\"TELEPRESENCE_CONTAINER\"]\n\t\t\tm := s.Mount\n\t\t\tw.volumes, w.err = docker.CreateVolumes(ctx, netip.AddrPortFrom(ud.DaemonInfo().ContainerIP, m.Port), container, mounts, m.ReadOnly)\n\t\t\tif w.err != nil {\n\t\t\t\tclog.Error(ctx, w.err)\n\t\t\t\treturn w\n\t\t\t}\n\t\t\tfor vol, path := range w.volumes {\n\t\t\t\tro := \"\"\n\t\t\t\tif m.ReadOnly || mounts.Get(\"\", path) == types.MountPolicyRemoteReadOnly {\n\t\t\t\t\tro = \":ro\"\n\t\t\t\t}\n\t\t\t\tourArgs = append(ourArgs, \"-v\", fmt.Sprintf(\"%s:%s%s\", vol, path, ro))\n\t\t\t\thasRemoteMounts = true\n\t\t\t}\n\t\t}\n\t}\n\n\tif hasRemoteMounts {\n\t\t// Give the mounter some time to effectively complete the remote mounts before we start the container that will use them.\n\t\ttime.Sleep(client.GetConfig(ctx).Intercept().MountCompletionDelay)\n\t}\n\n\targs = append(ourArgs, args...)\n\tw.cni, w.cmd, w.err = docker.Start(ctx, ud.Containerized(), args...)\n\treturn w\n}\n\ntype waiter struct {\n\tcmd *exec.Cmd\n\n\t// Info about the running container\n\tcni *docker.ContainerInfo\n\n\t// err is the error (if any) produced by the run\n\terr error\n\n\tmount *mount.Info\n\n\t// volume mounts as name -> path.\n\tvolumes map[string]string\n}\n\nfunc (w *waiter) wait(ctx context.Context) error {\n\tif w.err != nil {\n\t\tclog.Error(ctx, w.err)\n\t\treturn errcat.NoDaemonLogs.New(w.err)\n\t}\n\n\tvar exited, signalled atomic.Bool\n\tvolNames := make([]string, len(w.volumes))\n\ti := 0\n\tfor vol := range w.volumes {\n\t\tvolNames[i] = vol\n\t\ti++\n\t}\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tdone := make(chan error, 1)\n\n\tgo EnsureStopContainer(ctx, w.cni.Name, w.cni.ID, volNames, &exited, &signalled, done)\n\n\terr := w.cmd.Wait()\n\texited.Store(true)\n\tcancel()\n\twaitErr := <-done\n\tif signalled.Load() {\n\t\t// Errors caused by context or signal termination don't count.\n\t\terr = nil\n\t}\n\tif err == nil {\n\t\terr = waitErr\n\t}\n\treturn errcat.NoDaemonLogs.New(err)\n}\n\nfunc EnsureStopContainer(ctx context.Context, name, containerID string, volumes []string, exited, signalled *atomic.Bool, done chan<- error) {\n\tclog.Debugf(ctx, \"EnsureStopContainer %s\", name)\n\tdefer clog.Debugf(ctx, \"EnsureStopContainer %s ended\", name)\n\tdefer close(done)\n\tif len(volumes) > 0 {\n\t\tdefer func() {\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t\tctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)\n\t\t\tdefer cancel()\n\t\t\tdocker.RemoveVolumes(ctx, volumes)\n\t\t}()\n\t}\n\tsigCh := make(chan os.Signal, 1)\n\tsignal.Notify(sigCh, proc.SignalsToForward...)\n\tdefer func() {\n\t\tsignal.Stop(sigCh)\n\t}()\n\tselect {\n\tcase <-ctx.Done():\n\t\tclog.Debugf(ctx, \"EnsureStopContainer %s: Context done\", name)\n\tcase <-sigCh:\n\t\tclog.Debugf(ctx, \"EnsureStopContainer %s: Signalled\", name)\n\t}\n\tsignalled.Store(true)\n\tif exited.Load() {\n\t\treturn\n\t}\n\tctx = context.WithoutCancel(ctx)\n\terr := docker.StopContainer(ctx, containerID)\n\tif err != nil {\n\t\tif errdefs.IsNotFound(err) {\n\t\t\terr = nil\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"EnsureStopContainer %s: %w\", name, err)\n\t\t}\n\t}\n\tdone <- err\n}\n"
  },
  {
    "path": "pkg/client/cli/env/flags.go",
    "content": "package env\n\nimport (\n\t\"github.com/spf13/pflag\"\n)\n\ntype Flags struct {\n\tFile   string // --env-file\n\tSyntax Syntax // --env-syntax\n\tJSON   string // --env-json\n}\n\nfunc (f *Flags) AddFlags(flagSet *pflag.FlagSet) {\n\tflagSet.StringVarP(&f.File, \"env-file\", \"e\", \"\", ``+\n\t\t`Also emit the remote environment to an file. The syntax used in the file can be determined using flag --env-syntax`)\n\n\tflagSet.Var(&f.Syntax, \"env-syntax\", `Syntax used for env-file. One of `+SyntaxUsage())\n\n\tflagSet.StringVarP(&f.JSON, \"env-json\", \"j\", \"\", `Also emit the remote environment to a file as a JSON blob.`)\n}\n\nfunc (f *Flags) MaybeWrite(env map[string]string) error {\n\tif f.File != \"\" {\n\t\tif err := f.Syntax.writeFile(f.File, env); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tif f.JSON != \"\" {\n\t\tif err := SyntaxJSON.writeFile(f.JSON, env); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/env/syntax.go",
    "content": "package env\n\nimport (\n\t\"bufio\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n)\n\ntype Syntax int\n\nconst (\n\tSyntaxDocker Syntax = iota\n\tSyntaxCompose\n\tSyntaxSh\n\tSyntaxShExport\n\tSyntaxCsh\n\tSyntaxCshExport\n\tSyntaxPS\n\tSyntaxPSExport\n\tSyntaxCmd\n\tSyntaxJSON\n)\n\nvar syntaxNames = []string{ //nolint:gochecknoglobals // constant\n\t\"docker\",\n\t\"compose\",\n\t\"sh\",\n\t\"sh:export\",\n\t\"csh\",\n\t\"csh:export\",\n\t\"ps\",\n\t\"ps:export\",\n\t\"cmd\",\n\t\"json\",\n}\n\nfunc SyntaxUsage() string {\n\treturn `\"docker\", \"compose\", \"sh\", \"csh\", \"cmd\", \"json\", and \"ps\"; where \"sh\", \"csh\", and \"ps\" can be suffixed with \":export\"`\n}\n\n// Set uses a pointer receiver intentionally, even though the internal type is int, because\n// it must change the actual receiver value.\n//\n//goland:noinspection GoMixedReceiverTypes\nfunc (e *Syntax) Set(n string) error {\n\tex := slices.Index(syntaxNames, n)\n\tif ex < 0 {\n\t\treturn fmt.Errorf(\"invalid env syntax: %s\", n)\n\t}\n\t*e = Syntax(ex)\n\treturn nil\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (e Syntax) String() string {\n\tif e >= 0 && e <= SyntaxCmd {\n\t\treturn syntaxNames[e]\n\t}\n\treturn \"unknown\"\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (e Syntax) Type() string {\n\treturn \"string\"\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (e Syntax) writeFile(fileName string, env map[string]string) error {\n\tvar file *os.File\n\tif fileName == \"-\" {\n\t\tfile = os.Stdout\n\t} else {\n\t\tvar err error\n\t\tfile, err = os.Create(fileName)\n\t\tif err != nil {\n\t\t\treturn errcat.NoDaemonLogs.Errorf(err, \"failed to create environment file %q\", fileName)\n\t\t}\n\t}\n\treturn e.WriteToFileAndClose(file, env)\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (e Syntax) WriteToFileAndClose(file *os.File, env map[string]string) (err error) {\n\tif e == SyntaxJSON {\n\t\tdata, err := json.Marshal(env, jsontext.WithIndent(\"  \"))\n\t\tif err != nil {\n\t\t\t// Creating JSON from a map[string]string should never fail\n\t\t\tpanic(err)\n\t\t}\n\t\t_, err = file.Write(data)\n\t\treturn err\n\t}\n\n\tdefer file.Close()\n\tw := bufio.NewWriter(file)\n\n\tkeys := make([]string, len(env))\n\ti := 0\n\tfor k := range env {\n\t\tkeys[i] = k\n\t\ti++\n\t}\n\tsort.Strings(keys)\n\n\tfor _, k := range keys {\n\t\tr, err := e.WriteEntry(k, env[k])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif _, err = fmt.Fprintln(w, r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn w.Flush()\n}\n\n// WriteEntry will write the environment variable in a form that will make the target shell parse it correctly and verbatim.\n//\n//goland:noinspection GoMixedReceiverTypes\nfunc (e Syntax) WriteEntry(k, v string) (r string, err error) {\n\tswitch e {\n\tcase SyntaxDocker:\n\t\t// Docker does not accept multi-line environments\n\t\tif strings.IndexByte(v, '\\n') >= 0 {\n\t\t\treturn \"\", fmt.Errorf(\"docker run/build does not support multi-line environment values: key: %s, value %s\", k, v)\n\t\t}\n\t\tr = fmt.Sprintf(\"%s=%s\", k, v)\n\tcase SyntaxCompose:\n\t\tr = fmt.Sprintf(\"%s=%s\", k, quoteCompose(v))\n\tcase SyntaxSh:\n\t\tr = fmt.Sprintf(\"%s=%s\", k, shellquote.Unix(v))\n\tcase SyntaxShExport:\n\t\tr = fmt.Sprintf(\"export %s=%s\", k, shellquote.Unix(v))\n\tcase SyntaxCsh:\n\t\tr = fmt.Sprintf(\"set %s=%s\", k, shellquote.Unix(v))\n\tcase SyntaxCshExport:\n\t\tr = fmt.Sprintf(\"setenv %s %s\", k, shellquote.Unix(v))\n\tcase SyntaxPS:\n\t\tr = fmt.Sprintf(\"$Env:%s=%s\", k, quotePS(v))\n\tcase SyntaxPSExport:\n\t\tr = fmt.Sprintf(\"[Environment]::SetEnvironmentVariable(%s, %s, 'User')\", quotePS(k), quotePS(v))\n\tcase SyntaxCmd:\n\t\tif strings.IndexByte(v, '\\n') >= 0 {\n\t\t\treturn \"\", fmt.Errorf(\"cmd does not support multi-line environment values: key: %s, value %s\", k, v)\n\t\t}\n\t\tr = fmt.Sprintf(\"set %s=%s\", k, v)\n\tcase SyntaxJSON:\n\t\treturn \"\", errors.New(\"WriteEntry isn't supported for json\")\n\t}\n\treturn r, nil\n}\n\n// quotePS will put single quotes around the given value, which effectively removes all special meanings of\n// all contained characters, with one exception. Powershell uses pairs of single quotes to represent one single\n// quote in a quoted string.\nfunc quotePS(s string) string {\n\tsb := strings.Builder{}\n\tsb.WriteByte('\\'')\n\tfor _, c := range s {\n\t\tif c == '\\'' {\n\t\t\tsb.WriteByte('\\'')\n\t\t}\n\t\tsb.WriteRune(c)\n\t}\n\tsb.WriteByte('\\'')\n\treturn sb.String()\n}\n\n// quoteCompose checks if the give string contains characters that have special meaning for\n// docker compose. If it does, it will be quoted using either double or single quotes depending\n// on whether the string contains newlines, carriage returns, or tabs. Quotes within the value itself will\n// be escaped using backslash.\nfunc quoteCompose(s string) string {\n\tif s == \"\" {\n\t\treturn ``\n\t}\n\tq := byte('\\'')\n\tif strings.ContainsAny(s, \"\\n\\t\\r\") {\n\t\tq = '\"'\n\t} else if !shellquote.UnixEscape.MatchString(s) {\n\t\treturn s\n\t}\n\n\tsb := strings.Builder{}\n\tsb.WriteByte(q)\n\tfor _, c := range s {\n\t\tswitch c {\n\t\tcase rune(q):\n\t\t\tsb.WriteByte('\\\\')\n\t\t\tsb.WriteRune(c)\n\t\tcase '\\n':\n\t\t\tsb.WriteByte('\\\\')\n\t\t\tsb.WriteByte('n')\n\t\tcase '\\t':\n\t\t\tsb.WriteByte('\\\\')\n\t\t\tsb.WriteByte('t')\n\t\tcase '\\r':\n\t\t\tsb.WriteByte('\\\\')\n\t\t\tsb.WriteByte('r')\n\t\tdefault:\n\t\t\tsb.WriteRune(c)\n\t\t}\n\t}\n\tsb.WriteByte(q)\n\treturn sb.String()\n}\n"
  },
  {
    "path": "pkg/client/cli/env/syntax_test.go",
    "content": "package env\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSyntax_WriteEntry(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\te     Syntax\n\t\tkey   string\n\t\tvalue string\n\t\twant  string\n\t}{\n\t\t{\n\t\t\t`sh A=B C`,\n\t\t\tSyntaxSh,\n\t\t\t`A`,\n\t\t\t`B C`,\n\t\t\t`A='B C'`,\n\t\t},\n\t\t{\n\t\t\t`sh A=B \"C\"`,\n\t\t\tSyntaxSh,\n\t\t\t`A`,\n\t\t\t`B \"C\"`,\n\t\t\t`A='B \"C\"'`,\n\t\t},\n\t\t{\n\t\t\t`sh A=\"B C\"`,\n\t\t\tSyntaxSh,\n\t\t\t`A`,\n\t\t\t`\"B C\"`,\n\t\t\t`A='\"B C\"'`,\n\t\t},\n\t\t{\n\t\t\t`sh A=B 'C X'`,\n\t\t\tSyntaxSh,\n\t\t\t`A`,\n\t\t\t`B 'C X'`,\n\t\t\t`A='B '\\''C X'\\'`,\n\t\t},\n\t\t{\n\t\t\t`compose A=B 'C X'`,\n\t\t\tSyntaxCompose,\n\t\t\t`A`,\n\t\t\t`B 'C X'`,\n\t\t\t`A='B \\'C X\\''`,\n\t\t},\n\t\t{\n\t\t\t`compose A=B\\nC\\t\"D\"`,\n\t\t\tSyntaxCompose,\n\t\t\t`A`,\n\t\t\t\"B\\nC\\t\\\"D\\\"\",\n\t\t\t`A=\"B\\nC\\t\\\"D\\\"\"`,\n\t\t},\n\t\t{\n\t\t\t`sh A='B C'`,\n\t\t\tSyntaxSh,\n\t\t\t`A`,\n\t\t\t`'B C'`,\n\t\t\t`A=\\''B C'\\'`,\n\t\t},\n\t\t{\n\t\t\t`sh A=\\\"B\\\" \\\"C\\\"`,\n\t\t\tSyntaxSh,\n\t\t\t`A`,\n\t\t\t`\\\"B\\\" \\\"C\\\"`,\n\t\t\t`A='\\\"B\\\" \\\"C\\\"'`,\n\t\t},\n\t\t{\n\t\t\t`ps A=B C`,\n\t\t\tSyntaxPS,\n\t\t\t`A`,\n\t\t\t`B C`,\n\t\t\t`$Env:A='B C'`,\n\t\t},\n\t\t{\n\t\t\t`ps A='B C'`,\n\t\t\tSyntaxPS,\n\t\t\t`A`,\n\t\t\t`'B C'`,\n\t\t\t`$Env:A='''B C'''`,\n\t\t},\n\t\t{\n\t\t\t`ps:export A='B C'`,\n\t\t\tSyntaxPSExport,\n\t\t\t`A`,\n\t\t\t`'B C'`,\n\t\t\t`[Environment]::SetEnvironmentVariable('A', '''B C''', 'User')`,\n\t\t},\n\t\t{\n\t\t\t`ps:export A=B C`,\n\t\t\tSyntaxPSExport,\n\t\t\t`A`,\n\t\t\t`B C`,\n\t\t\t`[Environment]::SetEnvironmentVariable('A', 'B C', 'User')`,\n\t\t},\n\t\t{\n\t\t\t`ps:export A=\"B C\"`,\n\t\t\tSyntaxPSExport,\n\t\t\t`A`,\n\t\t\t`\"B C\"`,\n\t\t\t`[Environment]::SetEnvironmentVariable('A', '\"B C\"', 'User')`,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tr, err := tt.e.WriteEntry(tt.key, tt.value)\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tt.want, r)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/flags/addunique.go",
    "content": "package flags\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/spf13/pflag\"\n)\n\nfunc AddUnique(dst *pflag.FlagSet, src *pflag.FlagSet) (err error) {\n\tsrc.VisitAll(func(f *pflag.Flag) {\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif dst.Lookup(f.Name) != nil {\n\t\t\terr = fmt.Errorf(\"flag %q from flagset %q is already present in flagset %q\", f.Name, src.Name(), dst.Name())\n\t\t\treturn\n\t\t}\n\t\tif f.Shorthand != \"\" && dst.ShorthandLookup(f.Shorthand) != nil {\n\t\t\tf.Shorthand = \"\"\n\t\t}\n\t\tdst.AddFlag(f)\n\t})\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/cli/flags/context.go",
    "content": "package flags\n\nimport (\n\t\"context\"\n\n\t\"github.com/spf13/pflag\"\n)\n\ntype flagSetsKey struct{}\n\nfunc WithFlagSets(ctx context.Context, flagSets ...*pflag.FlagSet) context.Context {\n\tif len(flagSets) > 0 {\n\t\tctx = context.WithValue(ctx, flagSetsKey{}, flagSets)\n\t}\n\treturn ctx\n}\n\nfunc GetFlagSets(ctx context.Context) []*pflag.FlagSet {\n\tif fs, ok := ctx.Value(flagSetsKey{}).([]*pflag.FlagSet); ok {\n\t\treturn fs\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/flags/deprecation.go",
    "content": "package flags\n\nimport (\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\n// DeprecationIfChanged will print a deprecation warning on output.Info if the flag has changed.\n//\n// Use this method instead of the standard pflag deprecation to ensure that the deprecation message\n// doesn't clobber JSON output.\nfunc DeprecationIfChanged(cmd *cobra.Command, flagName, alternative string) {\n\tif flag := cmd.Flag(flagName); flag != nil && flag.Changed {\n\t\tioutil.Printf(output.Info(cmd.Context()), \"Flag --%s has been deprecated, %s\\n\", flagName, alternative)\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/flags/map.go",
    "content": "package flags\n\nimport (\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/slice\"\n)\n\n// Map returns a map of the flags that has been modified in the given FlagSet.\nfunc Map(flags *pflag.FlagSet) map[string]string {\n\tif flags == nil {\n\t\treturn nil\n\t}\n\tflagMap := make(map[string]string, flags.NFlag())\n\tflags.VisitAll(func(flag *pflag.Flag) {\n\t\tif flag.Changed {\n\t\t\tvar v string\n\t\t\tif sv, ok := flag.Value.(pflag.SliceValue); ok {\n\t\t\t\tv = slice.AsCSV(sv.GetSlice())\n\t\t\t} else {\n\t\t\t\tv = flag.Value.String()\n\t\t\t}\n\t\t\tflagMap[flag.Name] = v\n\t\t}\n\t})\n\treturn flagMap\n}\n"
  },
  {
    "path": "pkg/client/cli/flags/unparsed.go",
    "content": "package flags\n\nimport (\n\t\"fmt\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n)\n\n// GetUnparsedValue parses the given args for a matching option. The string value of the option and a boolean\n// indicating if the option was found. The function may also return an error for a malformed\n// option. Typically a non-bool option that lacks a value.\nfunc GetUnparsedValue(longForm string, shortForm byte, isBool bool, args []string) (string, bool, error) {\n\tv, found, _, err := ConsumeUnparsedValue(longForm, shortForm, isBool, slices.Clone(args))\n\treturn v, found, err\n}\n\n// GetUnparsedValues parses the given args for matching options, which may be repeated. The string values of the\n// option are collected into a slice.\nfunc GetUnparsedValues(longForm string, shortForm byte, args []string) ([]string, error) {\n\targs = slices.Clone(args)\n\tvar values []string\n\tfor {\n\t\tv, found, _, err := ConsumeUnparsedValue(longForm, shortForm, false, args)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !found {\n\t\t\tbreak\n\t\t}\n\t\tvalues = append(values, v)\n\t}\n\treturn values, nil\n}\n\n// ConsumeUnparsedValue parses the given args for a matching option. If found, the option and value\n// is removed from args. The string value of the option, a boolean indicating if the option was found,\n// the possibly modified args array is returned. The function may also return an error for a malformed\n// option. Typically a non-bool option that lacks a value.\nfunc ConsumeUnparsedValue(longForm string, shortForm byte, isBool bool, args []string) (string, bool, []string, error) {\n\tvar ixf func(string) bool\n\tif longForm != \"\" {\n\t\tlongFlag := \"--\" + longForm\n\t\tlongFlagV := longFlag + \"=\"\n\t\tif shortForm != 0 {\n\t\t\tixf = func(s string) bool {\n\t\t\t\treturn s == longFlag || strings.HasPrefix(s, longFlagV) || len(s) >= 2 && s[0] == '-' && s[1] != '-' && strings.IndexByte(s, shortForm) > 0\n\t\t\t}\n\t\t} else {\n\t\t\tixf = func(s string) bool {\n\t\t\t\treturn s == longFlag || strings.HasPrefix(s, longFlagV)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tif shortForm == 0 {\n\t\t\treturn \"\", false, args, nil\n\t\t}\n\t\tixf = func(s string) bool {\n\t\t\treturn len(s) >= 2 && s[0] == '-' && s[1] != '-' && strings.IndexByte(s, shortForm) > 0\n\t\t}\n\t}\n\tflagIndex := slices.IndexFunc(args, ixf)\n\tif flagIndex == -1 {\n\t\treturn \"\", false, args, nil\n\t}\n\n\tflag, val, valFound := strings.Cut(args[flagIndex], \"=\")\n\tfl := len(flag)\n\tif flag[1] == '-' || fl == 2 {\n\t\t// long form, or short-form as last character in option string. We treat those\n\t\t// the same\n\t\tswitch {\n\t\tcase valFound:\n\t\t\t// --flag=val\n\t\t\tif isBool && val == \"\" {\n\t\t\t\treturn \"\", false, args, fmt.Errorf(\"flag %q requires a value\", flag)\n\t\t\t}\n\t\t\treturn val, true, slices.Delete(args, flagIndex, flagIndex+1), nil\n\t\tcase isBool:\n\t\t\t// --flag\n\t\t\treturn \"true\", true, slices.Delete(args, flagIndex, flagIndex+1), nil\n\t\tcase flagIndex+1 < len(args) && !strings.HasPrefix(args[flagIndex+1], \"-\"):\n\t\t\t// --flag val\n\t\t\tval = args[flagIndex+1]\n\t\t\treturn val, true, slices.Delete(args, flagIndex, flagIndex+2), nil\n\t\tdefault:\n\t\t\treturn \"\", false, args, fmt.Errorf(\"flag %q requires a value\", flag)\n\t\t}\n\t}\n\n\t// Short form with several characters.\n\tif flag[fl-1] == shortForm {\n\t\t// short-form with the found flag last in the list.\n\t\tflag = flag[:fl-1]\n\t\tswitch {\n\t\tcase valFound:\n\t\t\t// return value of option 'z' and replace \"-xyz=val\" with \"-xy\"\n\t\t\targs[flagIndex] = flag\n\t\t\treturn val, true, args, nil\n\t\tcase isBool:\n\t\t\t// return true for option 'z' and replace \"-xyz\" with \"-xy\"\n\t\t\targs[flagIndex] = flag\n\t\t\treturn \"true\", true, args, nil\n\t\tcase flagIndex+1 < len(args) && !strings.HasPrefix(args[flagIndex+1], \"-\"):\n\t\t\t// return value of option 'z' and replace \"-xyz val\" with \"-xy\"\n\t\t\targs[flagIndex] = flag\n\t\t\tval = args[flagIndex+1]\n\t\t\treturn val, true, slices.Delete(args, flagIndex+1, flagIndex+2), nil\n\t\tdefault:\n\t\t\treturn \"\", false, args, fmt.Errorf(`flag \"-%c\" requires a value`, shortForm)\n\t\t}\n\t}\n\n\t// short-form, but the found flag not last in the option string\n\tif !isBool {\n\t\treturn \"\", false, args, fmt.Errorf(`flag \"-%c\" requires a value`, shortForm)\n\t}\n\tflag = strings.Replace(flag, string([]byte{shortForm}), \"\", 1)\n\tif valFound {\n\t\t// \"-xzy=val\" with \"-xy=val\"\n\t\targs[flagIndex] = flag + \"=\" + val\n\t} else {\n\t\t// \"-xzy=val\" with \"-xy\"\n\t\targs[flagIndex] = flag\n\t}\n\treturn \"true\", true, args, nil\n}\n\n// GetUnparsedBoolean returns the value of a boolean flag that has been provided after a \"--\" on the command\n// line, and hence hasn't been parsed as a normal flag. Typical use case is:\n//\n//\ttelepresence intercept --docker-run ... -- --rm\nfunc GetUnparsedBoolean(args []string, flag string) (bool, bool, error) {\n\tv, found, err := GetUnparsedValue(flag, 0, true, args)\n\tif !found || err != nil {\n\t\treturn false, false, err\n\t}\n\tbv, err := strconv.ParseBool(v)\n\tif err != nil {\n\t\treturn false, false, err\n\t}\n\treturn bv, true, nil\n}\n\nfunc HasOption(longForm string, shortForm byte, args []string) bool {\n\tlongFlag := \"--\" + longForm\n\treturn slices.ContainsFunc(args, func(s string) bool {\n\t\treturn s == longFlag || len(s) >= 2 && s[0] == '-' && s[1] != '-' && strings.IndexByte(s, shortForm) > 0\n\t})\n}\n"
  },
  {
    "path": "pkg/client/cli/flags/unparsed_test.go",
    "content": "package flags\n\nimport (\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestConsumeUnparsedFlagValue(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\targs      []string\n\t\tlongForm  string\n\t\tshortForm byte\n\t\tisBool    bool\n\t\twantFound bool\n\t\twantV     string\n\t\twantArgs  []string\n\t\twantErr   bool\n\t}{\n\t\t{\n\t\t\t\"empty value after =\",\n\t\t\t[]string{\"--name=\"},\n\t\t\t\"name\",\n\t\t\t0,\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\t\"\",\n\t\t\t[]string{},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"missing value at end of list\",\n\t\t\t[]string{\"--name\"},\n\t\t\t\"name\",\n\t\t\t0,\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t[]string{\"--name\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"missing value before next long-form option\",\n\t\t\t[]string{\"--name\", \"--other\"},\n\t\t\t\"name\",\n\t\t\t0,\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t[]string{\"--name\", \"--other\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"missing value before next short-form option\",\n\t\t\t[]string{\"--name\", \"-o\"},\n\t\t\t\"name\",\n\t\t\t0,\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t[]string{\"--name\", \"-o\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"long-form=option\",\n\t\t\t[]string{\"--name=value\"},\n\t\t\t\"name\",\n\t\t\t0,\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\t\"value\",\n\t\t\t[]string{},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"long-form=-option-\",\n\t\t\t[]string{\"--name=-value-\"},\n\t\t\t\"name\",\n\t\t\t0,\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\t\"-value-\",\n\t\t\t[]string{},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"long-form option\",\n\t\t\t[]string{\"--name\", \"value\"},\n\t\t\t\"name\",\n\t\t\t0,\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\t\"value\",\n\t\t\t[]string{},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"short-form alone\",\n\t\t\t[]string{\"-i\", \"value\", \"next\"},\n\t\t\t\"interactive\",\n\t\t\t'i',\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\t\"value\",\n\t\t\t[]string{\"next\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"short-form boolean alone\",\n\t\t\t[]string{\"-i\", \"next\"},\n\t\t\t\"interactive\",\n\t\t\t'i',\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\t\"true\",\n\t\t\t[]string{\"next\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"short-form last\",\n\t\t\t[]string{\"-abi\", \"value\", \"next\"},\n\t\t\t\"interactive\",\n\t\t\t'i',\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\t\"value\",\n\t\t\t[]string{\"-ab\", \"next\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"short-form boolean last\",\n\t\t\t[]string{\"-abi\", \"-x\"},\n\t\t\t\"interactive\",\n\t\t\t'i',\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\t\"true\",\n\t\t\t[]string{\"-ab\", \"-x\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"short-form first\",\n\t\t\t[]string{\"-abi\", \"value\", \"next\"},\n\t\t\t\"actual\",\n\t\t\t'a',\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\t\"\",\n\t\t\t[]string{\"-abi\", \"value\", \"next\"},\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"short-form boolean first\",\n\t\t\t[]string{\"-abi\", \"-x\"},\n\t\t\t\"actual\",\n\t\t\t'a',\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\t\"true\",\n\t\t\t[]string{\"-bi\", \"-x\"},\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"short-form boolean middle\",\n\t\t\t[]string{\"-abi\", \"-x\"},\n\t\t\t\"best\",\n\t\t\t'b',\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\t\"true\",\n\t\t\t[]string{\"-ai\", \"-x\"},\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotV, gotFound, gotArgs, err := ConsumeUnparsedValue(tt.longForm, tt.shortForm, tt.isBool, tt.args)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ConsumeUnparsedValue() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotV != tt.wantV {\n\t\t\t\tt.Errorf(\"ConsumeUnparsedValue() gotV = %v, want %v\", gotV, tt.wantV)\n\t\t\t}\n\t\t\tif gotFound != tt.wantFound {\n\t\t\t\tt.Errorf(\"ConsumeUnparsedValue() found = %t, want %t\", gotFound, tt.wantFound)\n\t\t\t}\n\t\t\tif !slices.Equal(gotArgs, tt.wantArgs) {\n\t\t\t\tt.Errorf(\"ConsumeUnparsedValue() args = %v, want %v\", gotArgs, tt.wantArgs)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetUnparsedFlagBoolean(t *testing.T) {\n\ttests := []struct {\n\t\targs    []string\n\t\tflag    string\n\t\twantV   bool\n\t\twantS   bool\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\t[]string{\"--rm=\"},\n\t\t\t\"rm\",\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm\"},\n\t\t\t\"rm\",\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm\", \"--other\"},\n\t\t\t\"rm\",\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm\", \"-o\"},\n\t\t\t\"rm\",\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm=value\"},\n\t\t\t\"rm\",\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm=true\"},\n\t\t\t\"rm\",\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm=true\"},\n\t\t\t\"rm\",\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm=True\"},\n\t\t\t\"rm\",\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm=1\"},\n\t\t\t\"rm\",\n\t\t\ttrue,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm=false\"},\n\t\t\t\"rm\",\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm=False\"},\n\t\t\t\"rm\",\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--rm=0\"},\n\t\t\t\"rm\",\n\t\t\tfalse,\n\t\t\ttrue,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t[]string{\"--do\"},\n\t\t\t\"rm\",\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t\tfalse,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(strings.Join(tt.args, \"_\"), func(t *testing.T) {\n\t\t\tgotV, gotS, err := GetUnparsedBoolean(tt.args, tt.flag)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetUnparsedBoolean() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif gotS != tt.wantS {\n\t\t\t\tt.Errorf(\"GetUnparsedBoolean() gotS = %v, want %v\", gotS, tt.wantS)\n\t\t\t}\n\t\t\tif gotV != tt.wantV {\n\t\t\t\tt.Errorf(\"GetUnparsedBoolean() gotV = %v, want %v\", gotV, tt.wantV)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/flags/zerovalue.go",
    "content": "package flags\n\n// IsZeroValue returns true if the given string represents a well-known zero value.\nfunc IsZeroValue(str string) bool {\n\tswitch str {\n\tcase \"\", \"false\", \"<nil>\", \"[]\", \"0\":\n\t\treturn true\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/client/cli/global/flags.go",
    "content": "package global\n\nimport (\n\t\"context\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nconst (\n\tFlagConfig   = \"config\"\n\tFlagContext  = \"context\"\n\tFlagDocker   = \"docker\"\n\tFlagNoReport = \"no-report\"\n\tFlagOutput   = \"output\"\n\tFlagProgress = \"progress\"\n\tFlagUse      = \"use\"\n)\n\nvar FlagNames = []string{FlagContext, FlagDocker, FlagNoReport, FlagOutput, FlagProgress, FlagUse} //nolint:gochecknoglobals // constant names\n\nfunc replaceHomeDir(ctx context.Context, dir string) string {\n\thomeEnv := \"$HOME\"\n\tif runtime.GOOS == \"windows\" {\n\t\thomeEnv = \"%USERPROFILE%\"\n\t}\n\treturn strings.Replace(dir, filelocation.UserHomeDir(ctx), homeEnv, 1)\n}\n\nfunc Flags(ctx context.Context, hasKubeFlags, markdown bool) *pflag.FlagSet {\n\tflags := pflag.NewFlagSet(\"\", 0)\n\tif !hasKubeFlags {\n\t\t// Add deprecated global connect and docker flags.\n\t\tflags.String(FlagContext, \"\", \"\")\n\t\tflags.Lookup(FlagContext).Hidden = true\n\t\tflags.Bool(FlagDocker, false, \"\")\n\t\tflags.Lookup(FlagDocker).Hidden = true\n\t}\n\tflags.Bool(FlagNoReport, false, \"\")\n\tf := flags.Lookup(FlagNoReport)\n\tf.Hidden = true\n\tf.Deprecated = \"not used\"\n\tflags.String(FlagUse, \"\", \"Match expression that uniquely identifies the daemon container\")\n\tflags.String(FlagOutput, \"default\", \"Set the output format, supported values are 'json', 'yaml', and 'default'\")\n\tflags.String(FlagProgress, \"auto\", `Set type of progress output (auto, tty, plain, json, quiet)`)\n\tappDir := filelocation.AppUserConfigDir(ctx)\n\tif markdown {\n\t\tappDir = replaceHomeDir(ctx, appDir)\n\t}\n\tflags.String(FlagConfig, filepath.Join(appDir, client.ConfigFile), `Path to the Telepresence configuration file`)\n\treturn flags\n}\n\nfunc SetProgressQuiet(cmd *cobra.Command) {\n\tpf := cmd.Flag(FlagProgress)\n\t_ = pf.Value.Set(\"quiet\")\n\tpf.Changed = true\n}\n\nfunc InitConfig(cmd *cobra.Command) error {\n\tctx := cmd.Context()\n\tif configFlag := cmd.Flag(FlagConfig); configFlag != nil {\n\t\tif configFile := configFlag.Value.String(); configFile != \"\" {\n\t\t\tctx = client.WithConfigFile(ctx, configFile)\n\t\t}\n\t}\n\tcfg, err := client.LoadConfig(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif client.ReplaceConfig(ctx, cfg) {\n\t\tclient.ReloadLogLevel(ctx)\n\t} else {\n\t\tctx = client.WithConfig(ctx, cfg)\n\t\tif !client.IsDaemon() {\n\t\t\tlogFile := filepath.Join(filelocation.AppUserLogDir(ctx), \"cli.log\")\n\t\t\tctx, err = logging.InitContext(ctx, logFile, cfg.LogLevels().CLI, logging.RotateDaily, false)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tcmd.SetContext(ctx)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/helm/chart.go",
    "content": "package helm\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/go-json-experiment/json\"\n\t\"helm.sh/helm/v3/pkg/action\"\n\t\"helm.sh/helm/v3/pkg/chart\"\n\t\"helm.sh/helm/v3/pkg/chart/loader\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\t\"helm.sh/helm/v3/pkg/cli\"\n\t\"helm.sh/helm/v3/pkg/registry\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/charts\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\nfunc loadCoreChart(version semver.Version) (*chart.Chart, error) {\n\tvar buf bytes.Buffer\n\tif err := charts.WriteChart(charts.DirTypeTelepresence, &buf, charts.TelepresenceChartName, version); err != nil {\n\t\treturn nil, err\n\t}\n\treturn loader.LoadArchive(&buf)\n}\n\nfunc newDefaultRegistryClient(ctx context.Context) (*registry.Client, error) {\n\treturn registry.NewClient(\n\t\tregistry.ClientOptEnableCache(true),\n\t\tregistry.ClientOptWriter(clog.StdLogger(ctx, slog.LevelDebug).Writer()),\n\t)\n}\n\nfunc withDownloadedChart(ctx context.Context, helmConfig *action.Configuration, ref string, version semver.Version, f func(string) error) error {\n\tclient, err := newDefaultRegistryClient(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdir, err := os.MkdirTemp(\"\", \"helm-\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\terr = os.RemoveAll(dir)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t}\n\t}()\n\tpull := action.NewPullWithOpts(action.WithConfig(helmConfig))\n\tpull.Version = version.String()\n\tpull.DestDir = dir\n\tpull.Settings = cli.New()\n\tpull.SetRegistryClient(client)\n\tout, err := pull.Run(ref)\n\tclog.Info(ctx, out)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn f(filepath.Join(dir, fmt.Sprintf(\"%s-%s.tgz\", charts.TelepresenceChartName, version)))\n}\n\nfunc pullCoreChart(ctx context.Context, helmConfig *action.Configuration, ref string, version semver.Version) (c *chart.Chart, err error) {\n\terr = withDownloadedChart(ctx, helmConfig, ref, version, func(path string) error {\n\t\tvar f *os.File\n\t\tf, err = os.Open(path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f.Close()\n\t\tc, err = loader.LoadArchive(f)\n\t\treturn err\n\t})\n\treturn c, err\n}\n\nfunc coalesceValues(ctx context.Context, req *Request) (map[string]any, error) {\n\t// OK, now install things.\n\tvar providedVals map[string]any\n\tif len(req.ValuesJson) > 0 {\n\t\tif err := json.Unmarshal(req.ValuesJson, &providedVals); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse values JSON: %w\", err)\n\t\t}\n\t}\n\n\tvar vals map[string]any\n\tif len(providedVals) > 0 {\n\t\tvals = chartutil.CoalesceTables(providedVals, GetValuesFunc(ctx, req))\n\t} else {\n\t\t// No values were provided. This means that an upgrade should retain existing values unless\n\t\t// reset-values is true.\n\t\tif req.Type == Upgrade && !req.ResetValues {\n\t\t\treq.ReuseValues = true\n\t\t}\n\t\tvals = GetValuesFunc(ctx, req)\n\t}\n\treturn vals, nil\n}\n\nfunc loadOrPullChart(ctx context.Context, helmConfig *action.Configuration, req *Request) (chrt *chart.Chart, vals map[string]any, err error) {\n\tvals, err = coalesceValues(ctx, req)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\ttmVer, err := getTrafficManagerVersion(vals)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tif req.Version != \"\" {\n\t\tver, err := semver.ParseTolerant(req.Version)\n\t\tif err != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"unable to parse chart version %q: %v\", req.Version, err)\n\t\t}\n\t\tif !ver.EQ(tmVer) {\n\t\t\tchrt, err = pullCoreChart(ctx, helmConfig, client.GetConfig(ctx).Helm().ChartURL, ver)\n\t\t\treturn chrt, vals, err\n\t\t}\n\t}\n\tchrt, err = loadCoreChart(tmVer)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"unable to load built-in helm chart: %w\", err)\n\t}\n\treturn chrt, vals, err\n}\n"
  },
  {
    "path": "pkg/client/cli/helm/install.go",
    "content": "package helm\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/go-json-experiment/json\"\n\t\"helm.sh/helm/v3/pkg/action\"\n\t\"helm.sh/helm/v3/pkg/chart\"\n\t\"helm.sh/helm/v3/pkg/chartutil\"\n\t\"helm.sh/helm/v3/pkg/cli\"\n\t\"helm.sh/helm/v3/pkg/cli/values\"\n\t\"helm.sh/helm/v3/pkg/getter\"\n\t\"helm.sh/helm/v3/pkg/release\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nconst (\n\thelmDriver                = \"secrets\"\n\ttrafficManagerReleaseName = agentconfig.ManagerAppName\n)\n\nvar GetValuesFunc = GetValues //nolint:gochecknoglobals // extension point\n\ntype RequestType int32\n\nconst (\n\tInstall RequestType = iota\n\tUpgrade\n\tUninstall\n\tLint\n)\n\ntype Request struct {\n\tvalues.Options\n\tType            RequestType\n\tValuesJson      []byte\n\tReuseValues     bool\n\tResetValues     bool\n\tCreateNamespace bool\n\tNoHooks         bool\n\tVersion         string\n\tKubeVersion     *chartutil.KubeVersion\n}\n\nfunc (hr *Request) Run(ctx context.Context, cr *connector.ConnectRequest) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\terr = errcat.NoDaemonLogs.New(err)\n\t\t}\n\t}()\n\tif hr.ReuseValues && hr.ResetValues {\n\t\treturn errors.New(\"--reset-values and --reuse-values are mutually exclusive\")\n\t}\n\n\tif cr.ManagerNamespace == \"\" {\n\t\tif ns, ok := cr.KubeFlags[\"namespace\"]; ok {\n\t\t\tcr.ManagerNamespace = ns\n\t\t} else {\n\t\t\tcr.ManagerNamespace = \"ambassador\"\n\t\t}\n\t}\n\tclog.Debugf(ctx, \"using manager namespace %q\", cr.ManagerNamespace)\n\n\tallValues, err := hr.MergeValues(getter.All(cli.New()))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\thr.ValuesJson, err = json.Marshal(allValues)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar config *k8s.Kubeconfig\n\tconfig, err = k8s.DaemonKubeconfig(ctx, cr)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar cluster *k8s.Cluster\n\tcluster, err = k8s.ConnectCluster(cr, config)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tmgrNs := k8s.GetManagerNamespace(cluster)\n\tswitch hr.Type {\n\tcase Uninstall:\n\t\terr = DeleteTrafficManager(ctx, cluster.Kubeconfig, mgrNs, false, hr)\n\tcase Lint:\n\t\terr = lint(ctx, cluster.Kubeconfig, mgrNs, hr)\n\tdefault:\n\t\tclog.Debug(ctx, \"ensuring that traffic-manager exists\")\n\t\terr = EnsureTrafficManager(cluster.Context, cluster.Kubeconfig, mgrNs, hr)\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar msg string\n\tswitch hr.Type {\n\tcase Install:\n\t\tmsg = \"installed\"\n\tcase Upgrade:\n\t\tmsg = \"upgraded\"\n\tcase Uninstall:\n\t\tmsg = \"uninstalled\"\n\tcase Lint:\n\t\treturn nil\n\t}\n\n\tupdatedResource := \"Traffic Manager\"\n\tioutil.Printf(dos.Stdout(ctx), \"\\n%s %s successfully\\n\", updatedResource, msg)\n\treturn nil\n}\n\nfunc getHelmConfig(ctx context.Context, clientGetter genericclioptions.RESTClientGetter, namespace string) (*action.Configuration, error) {\n\thelmConfig := &action.Configuration{}\n\terr := helmConfig.Init(clientGetter, namespace, helmDriver, func(format string, args ...any) {\n\t\tctx := clog.With(ctx, \"source\", \"helm\")\n\t\tclog.Infof(ctx, format, args...)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn helmConfig, nil\n}\n\nfunc GetValues(ctx context.Context, req *Request) map[string]any {\n\tclientConfig := client.GetConfig(ctx)\n\timgConfig := clientConfig.Images()\n\timageRegistry := imgConfig.Registry(ctx)\n\timageTag := req.Version\n\tif imageTag == \"\" {\n\t\timageTag = strings.TrimPrefix(client.Version(), \"v\")\n\t}\n\tvs := map[string]any{\n\t\t\"image\": map[string]any{\n\t\t\t\"registry\": imageRegistry,\n\t\t\t\"tag\":      imageTag,\n\t\t},\n\t}\n\tif !clientConfig.Grpc().MaxReceiveSizeV.IsZero() {\n\t\tvs[\"grpc\"] = map[string]any{\n\t\t\t\"maxReceiveSize\": clientConfig.Grpc().MaxReceiveSizeV.String(),\n\t\t}\n\t}\n\tif wai, wr := imgConfig.AgentImage(ctx), imgConfig.WebhookRegistry(ctx); wai != \"\" || wr != \"\" {\n\t\timage := make(map[string]any)\n\t\tif wai != \"\" {\n\t\t\ti := strings.LastIndexByte(wai, '/')\n\t\t\tif i >= 0 {\n\t\t\t\tif wr == \"\" {\n\t\t\t\t\twr = wai[:i]\n\t\t\t\t}\n\t\t\t\twai = wai[i+1:]\n\t\t\t}\n\t\t\tparts := strings.Split(wai, \":\")\n\t\t\tname := wai\n\t\t\ttag := \"\"\n\t\t\tif len(parts) > 1 {\n\t\t\t\tname = parts[0]\n\t\t\t\ttag = parts[1]\n\t\t\t}\n\t\t\timage[\"name\"] = name\n\t\t\timage[\"tag\"] = tag\n\t\t}\n\t\tif wr != \"\" {\n\t\t\timage[\"registry\"] = wr\n\t\t}\n\t\tvs[\"agent\"] = map[string]any{\"image\": image}\n\t}\n\treturn vs\n}\n\nfunc timedRun(ctx context.Context, run func(time.Duration) error) error {\n\ttimeouts := client.GetConfig(ctx).Timeouts()\n\tctx, cancel := timeouts.TimeoutContext(ctx, client.TimeoutHelm)\n\tdefer cancel()\n\n\trunResult := make(chan error, 1)\n\tgo func() {\n\t\trunResult <- run(timeouts.Get(client.TimeoutHelm))\n\t}()\n\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn client.CheckTimeout(ctx, ctx.Err())\n\tcase err := <-runResult:\n\t\tif err != nil {\n\t\t\terr = client.CheckTimeout(ctx, err)\n\t\t}\n\t\treturn err\n\t}\n}\n\nfunc installNew(\n\tctx context.Context,\n\tchrt *chart.Chart,\n\thelmConfig *action.Configuration,\n\treleaseName, namespace string,\n\treq *Request,\n\tvalues map[string]any,\n) error {\n\tclog.Infof(ctx, \"No existing %s found in namespace %s, installing %s...\", releaseName, namespace, chrt.Metadata.Version)\n\tinstall := action.NewInstall(helmConfig)\n\tinstall.ReleaseName = releaseName\n\tinstall.Namespace = namespace\n\tinstall.Atomic = true\n\tinstall.Wait = true\n\tinstall.CreateNamespace = req.CreateNamespace\n\tinstall.DisableHooks = req.NoHooks\n\tinstall.KubeVersion = req.KubeVersion\n\tinstall.Version = chrt.Metadata.Version\n\treturn timedRun(ctx, func(timeout time.Duration) error {\n\t\tinstall.Timeout = timeout\n\t\t_, err := install.Run(chrt, values)\n\t\treturn err\n\t})\n}\n\nfunc upgradeExisting(\n\tctx context.Context,\n\texistingVer string,\n\tchrt *chart.Chart,\n\thelmConfig *action.Configuration,\n\treleaseName, ns string,\n\treq *Request,\n\tvalues map[string]any,\n) error {\n\tclog.Infof(ctx, \"Existing Traffic Manager %s found in namespace %s, upgrading to %s...\", existingVer, ns, chrt.Metadata.Version)\n\tupgrade := action.NewUpgrade(helmConfig)\n\tupgrade.Atomic = true\n\tupgrade.Wait = true\n\tupgrade.Namespace = ns\n\tupgrade.ResetValues = req.ResetValues\n\tupgrade.ReuseValues = req.ReuseValues\n\tupgrade.DisableHooks = req.NoHooks\n\tupgrade.Version = chrt.Metadata.Version\n\treturn timedRun(ctx, func(timeout time.Duration) error {\n\t\tupgrade.Timeout = timeout\n\t\t_, err := upgrade.Run(releaseName, chrt, values)\n\t\treturn err\n\t})\n}\n\nfunc uninstallExisting(ctx context.Context, helmConfig *action.Configuration, releaseName, namespace string, req *Request) error {\n\tclog.Infof(ctx, \"Uninstalling %s in namespace %s\", releaseName, namespace)\n\tuninstall := action.NewUninstall(helmConfig)\n\tuninstall.DisableHooks = req.NoHooks\n\tuninstall.Wait = true\n\treturn timedRun(ctx, func(timeout time.Duration) error {\n\t\tuninstall.Timeout = timeout\n\t\t_, err := uninstall.Run(releaseName)\n\t\treturn err\n\t})\n}\n\nvar errStuck = errors.New(\"stuck in pending state\") //nolint:gochecknoglobals // constant\n\nfunc isInstalled(\n\tctx context.Context,\n\ttimeout time.Duration,\n\tclientGetter genericclioptions.RESTClientGetter,\n\treleaseName, namespace string,\n) (*release.Release, *action.Configuration, error) {\n\tclog.Debug(ctx, \"getHelmConfig\")\n\thelmConfig, err := getHelmConfig(ctx, clientGetter, namespace)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to initialize helm config: %w\", err)\n\t\treturn nil, nil, err\n\t}\n\n\tvar existing *release.Release\n\ttransitionStart := time.Now()\n\tfor time.Since(transitionStart) < timeout {\n\t\tclog.Debugf(ctx, \"getHelmRelease\")\n\t\tif existing, err = getHelmRelease(ctx, releaseName, helmConfig); err != nil {\n\t\t\t// If we weren't able to get the helm release at all, there's no hope for installing it\n\t\t\t// This could have happened because the user doesn't have the requisite permissions, or because there was some\n\t\t\t// kind of issue communicating with kubernetes. Let's hope it's the former and let's hope the traffic manager\n\t\t\t// is already set up. If it's the latter case (or the traffic manager isn't there), we'll be alerted by\n\t\t\t// a subsequent error anyway.\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tif existing == nil {\n\t\t\tclog.Infof(ctx, \"isInstalled(namespace=%q): current install: none\", namespace)\n\t\t\treturn nil, helmConfig, nil\n\t\t}\n\t\tst := existing.Info.Status\n\t\tif !(st.IsPending() || st == release.StatusUninstalling) {\n\t\t\towner := \"unknown\"\n\t\t\tif ow, ok := existing.Config[\"createdBy\"]; ok {\n\t\t\t\towner = ow.(string)\n\t\t\t}\n\t\t\tclog.Infof(ctx, \"isInstalled(namespace=%q): current install: version=%q, owner=%q, state.status=%q, state.desc=%q\",\n\t\t\t\tnamespace, releaseVer(existing), owner, st, existing.Info.Description)\n\t\t\treturn existing, helmConfig, nil\n\t\t}\n\t\tclog.Infof(ctx, \"isInstalled(namespace=%q): current install is in a pending or uninstalling state, waiting for it to transition...\",\n\t\t\tnamespace)\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\treturn existing, helmConfig, errStuck\n}\n\nfunc EnsureTrafficManager(ctx context.Context, clientGetter genericclioptions.RESTClientGetter, namespace string, req *Request) (err error) {\n\treturn ensureIsInstalled(ctx, clientGetter, trafficManagerReleaseName, namespace, req)\n}\n\n// EnsureTrafficManager ensures the traffic manager is installed.\nfunc ensureIsInstalled(\n\tctx context.Context, clientGetter genericclioptions.RESTClientGetter,\n\treleaseName, namespace string, req *Request,\n) error {\n\tcleanFailedState := func(helmConfig *action.Configuration) error {\n\t\turq := Request{\n\t\t\tType:    Uninstall,\n\t\t\tNoHooks: true,\n\t\t}\n\t\terr := uninstallExisting(ctx, helmConfig, releaseName, namespace, &urq)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"failed to clean up leftover release history: %w\", err)\n\t\t}\n\t\treturn err\n\t}\n\n\ttimeout := client.GetConfig(ctx).Timeouts().Get(client.TimeoutHelm)\n\texisting, helmConfig, err := isInstalled(ctx, timeout, clientGetter, releaseName, namespace)\n\tif err != nil {\n\t\tif !(errors.Is(err, errStuck) && req.Type == Install) {\n\t\t\treturn err\n\t\t}\n\t\tclog.Infof(ctx, \"ensureIsInstalled(namespace=%q): current install is has been in a pending state for longer than `timeouts.helm` (%v); \"+\n\t\t\t\"assuming it's stuck and will attempt uninstall\", namespace, timeout)\n\t\terr = cleanFailedState(helmConfig)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\texisting = nil\n\t}\n\n\t// Under various conditions, helm can leave the release history hanging around after the release is gone.\n\t// In those cases, uninstalling should clean everything up and leave us ready to install again\n\tif existing != nil && (existing.Info.Status != release.StatusDeployed) {\n\t\tclog.Infof(ctx, \"ensureIsInstalled(namespace=%q): current status (status=%q, desc=%q) is not %q, so assuming it's corrupt or stuck; removing it...\",\n\t\t\tnamespace, existing.Info.Status, existing.Info.Description, release.StatusDeployed)\n\t\terr = cleanFailedState(helmConfig)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\texisting = nil\n\t}\n\n\tchrt, vals, err := loadOrPullChart(ctx, helmConfig, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch {\n\tcase existing == nil && req.Type == Upgrade: // fresh install\n\t\terr = fmt.Errorf(\"%s is not installed, use 'telepresence helm install' to install it\", releaseName)\n\tcase existing == nil:\n\t\tclog.Infof(ctx, \"ensureIsInstalled(namespace=%q): performing fresh install...\", namespace)\n\t\terr = installNew(ctx, chrt, helmConfig, releaseName, namespace, req, vals)\n\tcase req.Type == Upgrade: // replace existing install\n\t\tclog.Infof(ctx, \"ensureIsInstalled(namespace=%q): replacing %s from %q to %q...\",\n\t\t\tnamespace, releaseName, releaseVer(existing), chrt.Metadata.AppVersion)\n\t\terr = upgradeExisting(ctx, releaseVer(existing), chrt, helmConfig, releaseName, namespace, req, vals)\n\tdefault:\n\t\terr = fmt.Errorf(\n\t\t\t\"%s version %q is already installed, use 'telepresence helm upgrade' instead to replace it\",\n\t\t\treleaseName, releaseVer(existing))\n\t}\n\treturn err\n}\n\n// DeleteTrafficManager deletes the traffic manager.\nfunc DeleteTrafficManager(\n\tctx context.Context, clientGetter genericclioptions.RESTClientGetter, namespace string, errOnFail bool, req *Request,\n) error {\n\terr := ensureIsDeleted(ctx, clientGetter, trafficManagerReleaseName, namespace, errOnFail, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc ensureIsDeleted(\n\tctx context.Context,\n\tclientGetter genericclioptions.RESTClientGetter,\n\treleaseName, namespace string,\n\terrOnFail bool,\n\treq *Request,\n) error {\n\thelmConfig, err := getHelmConfig(ctx, clientGetter, namespace)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to initialize helm config: %w\", err)\n\t}\n\n\texisting, err := getHelmRelease(ctx, releaseName, helmConfig)\n\tif err != nil {\n\t\terr := fmt.Errorf(\"unable to look for existing helm release in namespace %s: %w\", namespace, err)\n\t\tif errOnFail {\n\t\t\treturn err\n\t\t}\n\t\tclog.Infof(ctx, \"%s. Assuming it's already gone...\", err.Error())\n\t\treturn nil\n\t}\n\tif existing == nil {\n\t\terr := fmt.Errorf(\"%s in namespace %s already deleted\", releaseName, namespace)\n\t\tif errOnFail {\n\t\t\treturn err\n\t\t}\n\t\tclog.Info(ctx, err.Error())\n\t\treturn nil\n\t}\n\treturn uninstallExisting(ctx, helmConfig, releaseName, namespace, req)\n}\n\nfunc getTrafficManagerVersion(values map[string]any) (semver.Version, error) {\n\tif img, ok := values[\"image\"].(map[string]any); ok {\n\t\tif tag, ok := img[\"tag\"].(string); ok {\n\t\t\tv, err := semver.ParseTolerant(tag)\n\t\t\tif err != nil {\n\t\t\t\treturn v, fmt.Errorf(\"unable to parse chart value image.tag %q to a version: %w\", tag, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn version.Structured, nil\n}\n"
  },
  {
    "path": "pkg/client/cli/helm/lint.go",
    "content": "package helm\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"helm.sh/helm/v3/pkg/action\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\n\t\"github.com/telepresenceio/telepresence/v2/charts\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\n// lint checks that the chart is valid.\nfunc lint(ctx context.Context, clientGetter genericclioptions.RESTClientGetter, namespace string, req *Request) error {\n\tvals, err := coalesceValues(ctx, req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttmVer, err := getTrafficManagerVersion(vals)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif req.Version != \"\" {\n\t\tver, err := semver.ParseTolerant(req.Version)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"unable to parse chart version %q: %v\", req.Version, err)\n\t\t}\n\t\tif !ver.EQ(tmVer) {\n\t\t\thelmConfig, err := getHelmConfig(ctx, clientGetter, namespace)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to initialize helm config: %w\", err)\n\t\t\t}\n\t\t\treturn withDownloadedChart(ctx, helmConfig, client.GetConfig(ctx).Helm().ChartURL, ver, func(s string) error {\n\t\t\t\treturn runLint(s, namespace, vals, req)\n\t\t\t})\n\t\t}\n\t}\n\tfh, err := os.CreateTemp(\"\", fmt.Sprintf(\"%s-*.tgz\", charts.TelepresenceChartName))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = os.Remove(fh.Name())\n\t}()\n\terr = charts.WriteChart(charts.DirTypeTelepresence, fh, charts.TelepresenceChartName, tmVer)\n\tfh.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn runLint(fh.Name(), namespace, vals, req)\n}\n\nfunc runLint(path, namespace string, vals map[string]any, req *Request) error {\n\tlint := action.NewLint()\n\tlint.Namespace = namespace\n\tlint.Strict = true\n\tlint.KubeVersion = req.KubeVersion\n\tlr := lint.Run([]string{path}, vals)\n\tif err := errors.Join(lr.Errors...); err != nil {\n\t\treturn err\n\t}\n\tfor _, msg := range lr.Messages {\n\t\tioutil.Println(os.Stdout, msg)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/cli/helm/release.go",
    "content": "package helm\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"time\"\n\n\t\"helm.sh/helm/v3/pkg/action\"\n\t\"helm.sh/helm/v3/pkg/release\"\n)\n\n// getHelmRelease gets the traffic-manager helm release; if it is not found, it will return nil.\nfunc getHelmRelease(ctx context.Context, releaseName string, helmConfig *action.Configuration) (*release.Release, error) {\n\tlist := action.NewList(helmConfig)\n\tlist.Deployed = true\n\tlist.Failed = true\n\tlist.Pending = true\n\tlist.Uninstalled = true\n\tlist.Uninstalling = true\n\tlist.SetStateMask()\n\tvar releases []*release.Release\n\terr := timedRun(ctx, func(timeout time.Duration) error {\n\t\t// The List command never times out, so we need to do it here.\n\t\ttype rs struct {\n\t\t\terr error\n\t\t\trs  []*release.Release\n\t\t}\n\t\tdoneCh := make(chan rs)\n\t\tgo func() {\n\t\t\trels, err := list.Run()\n\t\t\tdoneCh <- rs{err: err, rs: rels}\n\t\t\tclose(doneCh)\n\t\t}()\n\t\tctx, cancel := context.WithTimeout(ctx, timeout)\n\t\tdefer cancel()\n\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ctx.Err()\n\t\tcase rr := <-doneCh:\n\t\t\tif rr.err != nil {\n\t\t\t\treturn rr.err\n\t\t\t}\n\t\t\treleases = rr.rs\n\t\t\treturn nil\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, r := range releases {\n\t\tif r.Name == releaseName {\n\t\t\treturn r, nil\n\t\t}\n\t}\n\treturn nil, nil\n}\n\nfunc releaseVer(rel *release.Release) string {\n\treturn strings.TrimPrefix(rel.Chart.Metadata.Version, \"v\")\n}\n"
  },
  {
    "path": "pkg/client/cli/ingest/command.go",
    "content": "package ingest\n\nimport (\n\t\"errors\"\n\t\"slices\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/env\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/mount\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n)\n\ntype Command struct {\n\tEnvFlags        env.Flags\n\tDockerFlags     docker.Flags\n\tMountFlags      mount.Flags\n\tWorkloadName    string // --workload || Command[0] // only valid if !localOnly\n\tContainerName   string // --container\n\tWaitMessage     string\n\tToPod           []string // --to-pod\n\tCmdline         []string\n\tFormattedOutput bool\n}\n\nfunc (c *Command) AddFlags(cmd *cobra.Command) {\n\tflagSet := cmd.Flags()\n\tflagSet.StringVarP(&c.ContainerName, \"container\", \"c\", \"\", \"Name of container that provides the environment and mounts for the ingest\")\n\n\tflagSet.StringSliceVar(&c.ToPod, \"to-pod\", []string{}, ``+\n\t\t`An additional port to forward from the ingested pod, will be made available at localhost:PORT `+\n\t\t`Use this to, for example, access proxy/helper sidecars in the ingested pod. The default protocol is TCP. `+\n\t\t`Use <port>/UDP for UDP ports`)\n\n\tc.EnvFlags.AddFlags(flagSet)\n\tc.MountFlags.AddFlags(flagSet, true)\n\tc.DockerFlags.AddFlags(flagSet, \"ingested\")\n\tflagSet.StringVar(&c.WaitMessage, \"wait-message\", \"\", \"Message to print when ingest handler has started\")\n\n\t_ = cmd.RegisterFlagCompletionFunc(\"container\", AutocompleteContainer)\n}\n\nfunc (c *Command) Validate(cmd *cobra.Command, positional []string) error {\n\tif len(positional) > 1 && cmd.Flags().ArgsLenAtDash() != 1 {\n\t\treturn errcat.User.New(\"commands to be run with ingest must come after options\")\n\t}\n\tc.WorkloadName = positional[0]\n\tc.Cmdline = positional[1:]\n\tc.FormattedOutput = output.WantsFormatted(cmd)\n\tif err := c.MountFlags.Validate(cmd); err != nil {\n\t\treturn err\n\t}\n\tif c.DockerFlags.Mount != \"\" && !c.MountFlags.Enabled {\n\t\treturn errors.New(\"--docker-mount cannot be used with --mount=false\")\n\t}\n\treturn c.DockerFlags.Validate(c.Cmdline)\n}\n\nfunc (c *Command) Run(cmd *cobra.Command, positional []string) error {\n\tif err := c.Validate(cmd, positional); err != nil {\n\t\treturn err\n\t}\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\treturn err\n\t}\n\tdefer progress.Stop(cmd.Context())\n\tctx := dos.WithStdio(cmd.Context(), cmd)\n\treturn NewState(c, c.MountFlags.ValidateConnected(ctx)).Run(ctx)\n}\n\nfunc AutocompleteContainer(cmd *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {\n\tif len(args) == 0 {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tctx, s, err := connect.GetOptionalSession(cmd)\n\tif s == nil || err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tsc, err := s.GetAgentConfig(ctx, args[0])\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tcss := make([]string, 0, len(sc.Containers))\n\tvar svcName string\n\tif sf := cmd.Flags().Lookup(\"service\"); sf != nil && sf.Changed {\n\t\t// Only include containers matching this service\n\t\tsvcName = sf.Value.String()\n\t}\n\tfor _, c := range sc.Containers {\n\t\tif svcName == \"\" || slices.ContainsFunc(c.Intercepts, func(ix *agentconfig.Intercept) bool { return ix.ServiceName == svcName }) {\n\t\t\tcss = append(css, c.Name)\n\t\t}\n\t}\n\treturn css, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace\n}\n"
  },
  {
    "path": "pkg/client/cli/ingest/info.go",
    "content": "package ingest\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"strings\"\n\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/mount\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype Info struct {\n\tWorkloadName string            `json:\"workload_name,omitempty\"   yaml:\"workload_name,omitempty\"`\n\tWorkloadKind string            `json:\"workload_kind,omitempty\"   yaml:\"workload_kind,omitempty\"`\n\tContainer    string            `json:\"container,omitempty\"       yaml:\"container,omitempty\"`\n\tEnvironment  map[string]string `json:\"environment,omitempty\"     yaml:\"environment,omitempty\"`\n\tMount        *mount.Info       `json:\"mount,omitempty\"           yaml:\"mount,omitempty\"`\n\tPodIP        string            `json:\"pod_ip,omitempty\"          yaml:\"pod_ip,omitempty\"`\n}\n\nfunc NewInfo(ctx context.Context, ii *rpc.IngestInfo, mountError error) *Info {\n\tvar m *mount.Info\n\tif mountError != nil {\n\t\tm = &mount.Info{Error: mountError.Error()}\n\t} else if ii.MountPoint != \"\" {\n\t\tm = mount.NewInfo(ctx,\n\t\t\tii.Environment, uint16(ii.FtpPort), uint16(ii.SftpPort), ii.ClientMountPoint, ii.MountPoint, ii.PodIp, types.MountPoliciesFromRPC(ii.Mounts), true)\n\t}\n\treturn &Info{\n\t\tWorkloadName: ii.Workload,\n\t\tWorkloadKind: ii.WorkloadKind,\n\t\tContainer:    ii.Container,\n\t\tMount:        m,\n\t\tPodIP:        ii.PodIp,\n\t\tEnvironment:  ii.Environment,\n\t}\n}\n\nfunc (ii *Info) String() string {\n\tsb := strings.Builder{}\n\t_, _ = ii.WriteTo(&sb)\n\treturn sb.String()\n}\n\nfunc (ii *Info) WriteTo(w io.Writer) (int64, error) {\n\tkvf := ioutil.DefaultKeyValueFormatter()\n\tkvf.Prefix = \"   \"\n\tkvf.Add(\"Workload name\", ii.WorkloadName)\n\tkvf.Add(\"Workload kind\", ii.WorkloadKind)\n\tkvf.Add(\"Container name\", ii.Container)\n\tif m := ii.Mount; m != nil {\n\t\tif m.LocalDir != \"\" {\n\t\t\tkvf.Add(\"Volume Mount Point\", m.LocalDir)\n\t\t} else if m.Error != \"\" {\n\t\t\tkvf.Add(\"Volume Mount Error\", m.Error)\n\t\t}\n\t}\n\treturn kvf.WriteTo(w)\n}\n"
  },
  {
    "path": "pkg/client/cli/ingest/state.go",
    "content": "package ingest\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\n\tgrpcCodes \"google.golang.org/grpc/codes\"\n\tgrpcStatus \"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\tcliDocker \"github.com/telepresenceio/telepresence/v2/pkg/client/cli/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype State interface {\n\tCreateRequest() (*rpc.IngestRequest, error)\n\tRun(context.Context) error\n\tRunAndLeave() bool\n}\n\ntype state struct {\n\t*Command\n\tmountError       error\n\tinfo             *rpc.IngestInfo\n\thandlerContainer string\n\n\t// Possibly extended version of the state. Use when calling interface methods.\n\tself State\n}\n\nfunc NewState(\n\targs *Command,\n\tmountError error,\n) State {\n\ts := &state{\n\t\tCommand:    args,\n\t\tmountError: mountError,\n\t}\n\ts.self = s\n\treturn s\n}\n\nfunc (s *state) SetSelf(self State) {\n\ts.self = self\n}\n\nfunc (s *state) CreateRequest() (*rpc.IngestRequest, error) {\n\tir := &rpc.IngestRequest{\n\t\tIdentifier: &rpc.IngestIdentifier{\n\t\t\tWorkloadName:  s.WorkloadName,\n\t\t\tContainerName: s.ContainerName,\n\t\t},\n\t\tLocalMountPort: int32(s.MountFlags.LocalMountPort),\n\t\tMountPoint:     s.MountFlags.Mount,\n\t}\n\n\tfor _, toPod := range s.ToPod {\n\t\tpp, err := types.ParsePortAndProto(toPod)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tir.LocalPorts = append(ir.LocalPorts, pp.String())\n\t}\n\treturn ir, nil\n}\n\nfunc (s *state) RunAndLeave() bool {\n\treturn len(s.Cmdline) > 0 || s.DockerFlags.Run\n}\n\nfunc (s *state) Run(ctx context.Context) error {\n\tprogress.Start(ctx, \"Initializing\")\n\tvar err error\n\tif !s.RunAndLeave() {\n\t\treturn client.WithEnsuredState(ctx, s.create, nil, nil)\n\t}\n\n\t// start intercept, run command, then leave the intercept\n\tif s.DockerFlags.Run {\n\t\tvar defaultContainerName string\n\t\tif len(s.ContainerName) > 0 {\n\t\t\tdefaultContainerName = fmt.Sprintf(\"ingest-%s-%s\", s.WorkloadName, s.ContainerName)\n\t\t} else {\n\t\t\tdefaultContainerName = fmt.Sprintf(\"ingest-%s\", s.WorkloadName)\n\t\t}\n\t\terr = s.DockerFlags.PullOrBuildImage(progress.WithEventId(ctx, \"Handler\"))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.handlerContainer, s.Cmdline, err = s.DockerFlags.GetContainerNameAndArgs(defaultContainerName)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn client.WithEnsuredState(ctx, s.create, s.runCommand, s.leave)\n}\n\nfunc (s *state) create(ctx context.Context) (acquired bool, err error) {\n\tud := daemon.MustGetUserClient(ctx)\n\tir, err := s.self.CreateRequest()\n\tif err != nil {\n\t\treturn false, errcat.NoDaemonLogs.New(err)\n\t}\n\n\tif ir.MountPoint != \"\" {\n\t\tdefer func() {\n\t\t\tif !acquired && runtime.GOOS != \"windows\" {\n\t\t\t\t// remove if empty\n\t\t\t\t_ = os.Remove(ir.MountPoint)\n\t\t\t}\n\t\t}()\n\t}\n\n\tprogress.Start(ctx, \"Creating\")\n\tdefer progress.Stop(ctx)\n\n\t// Submit the request\n\tctx = progress.WithEventId(ctx, ud.DaemonID().Name)\n\tprogress.Working(ctx, types.EngagementTypeIngest.Working())\n\tii, err := ud.Ingest(ctx, ir)\n\tif err != nil {\n\t\treturn false, progress.MaybeWriteError(ctx, grpc.FromGRPC(err))\n\t}\n\n\tif s.MountFlags.Enabled {\n\t\tif ir.LocalMountPort != 0 {\n\t\t\tii.PodIp = \"127.0.0.1\"\n\t\t\tii.SftpPort = ir.LocalMountPort\n\t\t}\n\t} else {\n\t\tii.MountPoint = \"\"\n\t\tii.FtpPort = 0\n\t\tii.SftpPort = 0\n\t}\n\ts.info = ii\n\n\tprogress.Done(ctx, types.EngagementTypeIngest.WorkDone())\n\tprogress.Infof(ctx, \"Using %s %s\", ii.WorkloadKind, ii.Workload)\n\n\tenv := s.info.Environment\n\tif env == nil {\n\t\tenv = make(map[string]string)\n\t\ts.info.Environment = env\n\t}\n\tenv[\"TELEPRESENCE_ROOT\"] = s.info.ClientMountPoint\n\tif err = s.EnvFlags.MaybeWrite(env); err != nil {\n\t\treturn true, err\n\t}\n\ts.ContainerName = env[\"TELEPRESENCE_CONTAINER\"]\n\tinfo := NewInfo(ctx, ii, nil)\n\tif s.FormattedOutput {\n\t\toutput.Object(ctx, info, true)\n\t} else {\n\t\tprogress.Info(ctx, info)\n\t}\n\treturn true, nil\n}\n\nfunc (s *state) leave(ctx context.Context) error {\n\tud := daemon.MustGetUserClient(ctx)\n\tctx = progress.WithEventId(ctx, ud.DaemonID().Name)\n\tprogress.Working(ctx, \"Ending ingest\")\n\t_, err := ud.LeaveIngest(ctx, &rpc.IngestIdentifier{\n\t\tWorkloadName:  s.WorkloadName,\n\t\tContainerName: s.ContainerName,\n\t})\n\tif err != nil && grpcStatus.Code(err) == grpcCodes.Canceled {\n\t\t// Deactivation was caused by a disconnect\n\t\terr = nil\n\t}\n\tif err != nil {\n\t\terr = grpc.FromGRPC(err)\n\t\terr = progress.MaybeWriteError(ctx, err)\n\t} else {\n\t\tprogress.Done(ctx, \"Ended ingest\")\n\t}\n\treturn err\n}\n\nfunc (s *state) runCommand(ctx context.Context) error {\n\t// start the interceptor process\n\tprogress.Start(ctx, \"Starting\")\n\tdefer progress.Stop(ctx)\n\n\tud := daemon.MustGetUserClient(ctx)\n\tif !s.DockerFlags.Run {\n\t\tenv := s.info.Environment\n\t\tcmd, err := proc.Start(ctx, env, s.Cmdline[0], s.Cmdline[1:]...)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"error interceptor starting process: %v\", err)\n\t\t\treturn errcat.NoDaemonLogs.New(err)\n\t\t}\n\t\tif err = ud.AddHandler(ctx, fmt.Sprintf(\"%s/%s\", s.WorkloadName, s.handlerContainer), cmd, \"\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// The external command will not output anything to the logs. An error here\n\t\t// is likely caused by the user hitting <ctrl>-C to terminate the process.\n\t\treturn errcat.NoDaemonLogs.New(proc.Wait(ctx, func() {}, cmd))\n\t}\n\n\tii := NewInfo(ctx, s.info, s.mountError)\n\tii.Environment[\"TELEPRESENCE_INTERCEPT_ID\"] = s.WorkloadName + \"/\" + s.ContainerName\n\tii.Environment[agentconfig.EnvAPIHost] = ud.DaemonID().ContainerName()\n\tdr := cliDocker.Runner{\n\t\tFlags:         s.DockerFlags,\n\t\tContainerName: s.handlerContainer,\n\t\tEnvironment:   ii.Environment,\n\t\tMount:         ii.Mount,\n\t}\n\treturn dr.Run(ctx, s.WaitMessage, s.Cmdline...)\n}\n"
  },
  {
    "path": "pkg/client/cli/intercept/command.go",
    "content": "package intercept\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/client-go/kubernetes\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/connect\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/env\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ingest\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/mount\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/matcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype Command struct {\n\tEnvFlags      env.Flags\n\tDockerFlags   docker.Flags\n\tMountFlags    mount.Flags\n\tName          string   // Command[0] || `${Command[0]}-${--namespace}` // which depends on a combinationof --workload and --namespace\n\tAgentName     string   // --workload || Command[0] // only valid if !localOnly\n\tPorts         []string // --port\n\tServiceName   string   // --service\n\tContainerName string   // --container\n\tAddress       string   // --address\n\n\tReplace bool // whether --replace was passed\n\tWiretap bool // wiretap subcommand used\n\n\tToPod []string // --to-pod\n\n\tCmdline []string // Command[1:]\n\n\tMechanism       string // --mechanism tcp\n\tMechanismArgs   []string\n\tExtendedInfo    []byte\n\tWaitMessage     string // Message printed when a containerized intercept handler is started and waiting for an interrupt\n\tFormattedOutput bool\n\tDetailedOutput  bool\n\tNoDefaultPort   bool\n\n\t// Telepresence API server fields\n\tMetadata []string // --metadata key=value pairs for metadata\n\n\t// HTTP Intercepts fields\n\tHTTPHeaderFilters     []string // --http-header key=value pairs for HTTP header filtering\n\tHTTPPathEqualFilters  []string // --http-path-equal paths for HTTP path filtering (exact match)\n\tHTTPPathPrefixFilters []string // --http-path-prefix paths for HTTP path filtering (prefix match)\n\tHTTPPathRegexFilters  []string // --http-path-regex paths for HTTP path filtering (regex match)\n\tPlaintext             bool     // --plaintext use plaintext instead of TLS when communicating with the intercept handler\n}\n\n// UsesHTTPMechanism returns true if any HTTP-specific flags were provided,\n// indicating that HTTP-aware interception should be used.\nfunc (c *Command) UsesHTTPMechanism() bool {\n\treturn len(c.HTTPHeaderFilters) > 0 ||\n\t\tlen(c.HTTPPathEqualFilters) > 0 ||\n\t\tlen(c.HTTPPathPrefixFilters) > 0 ||\n\t\tlen(c.HTTPPathRegexFilters) > 0\n}\n\n// parseKeyValue parses a string representation of a key and value that can use either \"=\" or \":\" as separator.\n// Supports both formats:\n//   - \"X-User-ID=dev123\" (equals format)\n//   - \"X-User-ID: dev123\" (colon format, compatible with curl -H)\n//\n// Returns the key and value, or an error if the format is invalid.\n// When both separators are present, colon takes precedence (standard HTTP format).\nfunc parseKeyValue(kv string) (string, string, error) {\n\t// Try colon separator first (standard HTTP format, curl -H compatible)\n\tif key, value, ok := tryParseHeaderWithSeparator(kv, \":\"); ok {\n\t\treturn key, value, nil\n\t}\n\n\t// Try equals separator\n\tif key, value, ok := tryParseHeaderWithSeparator(kv, \"=\"); ok {\n\t\treturn key, value, nil\n\t}\n\n\t// Neither separator found\n\treturn \"\", \"\", fmt.Errorf(\"invalid format '%s': must be key=value or key: value\", kv)\n}\n\n// tryParseHeaderWithSeparator attempts to parse a header with the given separator.\n// Returns the key, value, and true if successful; empty strings and false otherwise.\nfunc tryParseHeaderWithSeparator(header, separator string) (string, string, bool) {\n\tparts := strings.SplitN(header, separator, 2)\n\tif len(parts) != 2 {\n\t\treturn \"\", \"\", false\n\t}\n\n\tkey := strings.TrimSpace(parts[0])\n\tif key == \"\" {\n\t\treturn \"\", \"\", false\n\t}\n\n\tvalue := strings.TrimSpace(parts[1])\n\treturn key, value, true\n}\n\nfunc (c *Command) AddInterceptFlags(cmd *cobra.Command) {\n\twhat := \"intercept\"\n\thow := \"intercepted\"\n\tif c.Wiretap {\n\t\twhat = \"wiretap\"\n\t\thow = \"wiretapped\"\n\t}\n\tflagSet := cmd.Flags()\n\tflagSet.StringVarP(&c.AgentName, \"workload\", \"w\", \"\", fmt.Sprintf(\"Name of workload (Deployment, ReplicaSet, StatefulSet, Rollout) to %s, if different from <name>\", what))\n\tflagSet.StringSliceVarP(&c.Ports, \"port\", \"p\", nil, ``+\n\t\t`Local ports to forward to. Use <local port>:<identifier> to uniquely identify service ports, where the <identifier> is the port name or number. `+\n\t\t`With --docker-run and a daemon that doesn't run in docker', use <local port>:<container port> or `+\n\t\t`<local port>:<container port>:<identifier>.`,\n\t)\n\n\tflagSet.StringVar(&c.Address, \"address\", \"\", ``+\n\t\t`Local address to forward to, e.g. '--address 10.0.0.2' (default \"127.0.0.1\" or name of container)`,\n\t)\n\n\tflagSet.StringVar(&c.ServiceName, \"service\", \"\", fmt.Sprintf(\"Optional name of service to %s. Sometimes needed to uniquely identify the intercepted port.\", what))\n\n\tflagSet.StringVar(&c.ContainerName, \"container\", \"\",\n\t\tfmt.Sprintf(\"Name of container that provides the environment and mounts for the %s. Defaults to the container matching the first %s port.\", what, how))\n\n\tif !c.Wiretap {\n\t\tflagSet.StringSliceVar(&c.Metadata, \"metadata\", nil, fmt.Sprintf(``+\n\t\t\t`Metadata to attach to the %s. Use --metadata key=value to set a single key/value pair, or --metadata key1=value1 --metadata key2=value2 to set `+\n\t\t\t`multiple key/value pairs. The metadata can be retrieved using the Telepresence API server.`, what))\n\t\tflagSet.StringSliceVar(&c.ToPod, \"to-pod\", []string{}, fmt.Sprintf(``+\n\t\t\t`Additional ports to forward to the %s pod, will available for connections to localhost:PORT. `+\n\t\t\t`Use this to, for example, access proxy/helper sidecars in the %s pod. The default protocol is TCP. `+\n\t\t\t`Use <port>/UDP for UDP ports`, how, how))\n\t}\n\n\tc.EnvFlags.AddFlags(flagSet)\n\tc.MountFlags.AddFlags(flagSet, false)\n\tc.DockerFlags.AddFlags(flagSet, how)\n\n\tflagSet.StringVar(&c.Mechanism, \"mechanism\", \"tcp\", \"Which extension `mechanism` to use\")\n\n\tflagSet.StringVar(&c.WaitMessage, \"wait-message\", \"\", fmt.Sprintf(\"Message to print when %s handler has started\", what))\n\n\tflagSet.BoolVar(&c.DetailedOutput, \"detailed-output\", false,\n\t\tfmt.Sprintf(`Provide very detailed info about the %s when used together with --output=json or --output=yaml'`, what))\n\n\tif !c.Wiretap {\n\t\tflagSet.BoolVarP(&c.Replace, \"replace\", \"\", false,\n\t\t\t`Indicates if the traffic-agent should replace application containers in workload pods. `+\n\t\t\t\t`The default behavior is for the agent sidecar to be installed alongside existing containers.`)\n\t\tflagSet.Lookup(\"replace\").Deprecated = \"Use the replace command.\"\n\t}\n\n\t// HTTP Intercepts flags\n\tflagSet.StringSliceVar(&c.HTTPHeaderFilters, \"http-header\", nil,\n\t\tfmt.Sprintf(`HTTP header filters. Only requests with matching headers will be %s. `+\n\t\t\t`Supports both formats: --http-header \"X-User-ID=dev123\" or --http-header \"X-User-ID: dev123\" (curl -H compatible). `+\n\t\t\t`Multiple headers use AND logic.`, how))\n\n\tflagSet.StringSliceVar(&c.HTTPPathEqualFilters, \"http-path-equal\", nil,\n\t\tfmt.Sprintf(`HTTP path filters. Only requests with matching paths will be %s. `+\n\t\t\t`Exact path matching.`, how))\n\n\tflagSet.StringSliceVar(&c.HTTPPathPrefixFilters, \"http-path-prefix\", nil,\n\t\tfmt.Sprintf(`HTTP path prefix filters. Only requests with matching path prefixes will be %s.`, how))\n\n\tflagSet.StringSliceVar(&c.HTTPPathRegexFilters, \"http-path-regex\", nil,\n\t\tfmt.Sprintf(`HTTP path regex filters. Only requests with paths matching the regex will be %s.`, how))\n\n\tflagSet.BoolVar(&c.Plaintext, \"plaintext\", false, \"Use plaintext instead of TLS when communicating with the intercept handler\")\n\n\t_ = cmd.RegisterFlagCompletionFunc(\"container\", ingest.AutocompleteContainer)\n\t_ = cmd.RegisterFlagCompletionFunc(\"service\", autocompleteService)\n}\n\nfunc (c *Command) AddReplaceFlags(cmd *cobra.Command) {\n\tflagSet := cmd.Flags()\n\tflagSet.StringSliceVarP(&c.Ports, \"port\", \"p\", []string{\"all\"}, ``+\n\t\t`Local ports to forward to. Use <local port>:<identifier> to uniquely identify container ports, where the <identifier> is the port name or number. `+\n\t\t`Use \"all\" (the default) to forward all ports declared in the replaced container to their corresponding local port. `,\n\t)\n\n\tflagSet.StringVar(&c.Address, \"address\", \"\", ``+\n\t\t`Local address to forward to, e.g. '--address 10.0.0.2' (default \"127.0.0.1\" or name of container)`,\n\t)\n\n\tflagSet.StringVar(&c.ContainerName, \"container\", \"\",\n\t\t\"Name of container that should be replaced. Can be omitted if the workload only has one container.\")\n\n\tflagSet.StringSliceVar(&c.ToPod, \"to-pod\", []string{}, ``+\n\t\t`Additional ports to forward to the pod containing the replaced container, will available for connections to localhost:PORT. `+\n\t\t`Use this to, for example, access proxy/helper sidecars in the pod. The default protocol is TCP. `+\n\t\t`Use <port>/UDP for UDP ports`)\n\n\tc.EnvFlags.AddFlags(flagSet)\n\tc.MountFlags.AddFlags(flagSet, false)\n\tc.DockerFlags.AddFlags(flagSet, \"replaced\")\n\n\tflagSet.StringVar(&c.WaitMessage, \"wait-message\", \"\", \"Message to print when replace handler has started\")\n\n\tflagSet.BoolVar(&c.DetailedOutput, \"detailed-output\", false,\n\t\t`Provide very detailed info about the replace when used together with --output=json or --output=yaml'`)\n\n\t_ = cmd.RegisterFlagCompletionFunc(\"container\", ingest.AutocompleteContainer)\n}\n\nfunc (c *Command) Validate(cmd *cobra.Command, positional []string) error {\n\tdashIndex := cmd.Flags().ArgsLenAtDash()\n\tif dashIndex < 0 {\n\t\tswitch len(positional) {\n\t\tcase 0:\n\t\t\treturn errcat.User.New(\"intercept expects exactly one argument (the name for the intercept)\")\n\t\tcase 1:\n\t\t\tc.Name = positional[0]\n\t\tdefault:\n\t\t\treturn errcat.User.New(`intercept expects exactly one argument (flags and arguments to docker must be added after \"--\"`)\n\t\t}\n\t} else {\n\t\tif dashIndex == 1 {\n\t\t\tc.Name = positional[0]\n\t\t\tc.Cmdline = positional[dashIndex:]\n\t\t} else {\n\t\t\treturn errcat.User.New(`intercept expects exactly one argument before \"--\" (the name for the intercept)`)\n\t\t}\n\t}\n\tc.FormattedOutput = output.WantsFormatted(cmd)\n\n\tfor _, meta := range c.Metadata {\n\t\tif _, _, err := parseKeyValue(meta); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// HTTP Intercepts: validate header format\n\tif c.UsesHTTPMechanism() {\n\t\tfor _, header := range c.HTTPHeaderFilters {\n\t\t\tif _, _, err := parseKeyValue(header); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// Validate HTTP mechanisms aren't used with UDP ports\n\t\tfor _, portSpec := range c.Ports {\n\t\t\tif pp, err := types.ParsePortAndProto(portSpec); err == nil && pp.Proto == types.ProtoUDP {\n\t\t\t\treturn errcat.User.Newf(\"HTTP filters cannot be used with UDP port %s\", portSpec)\n\t\t\t}\n\t\t}\n\n\t\t// Auto-detect and set mechanism to \"http\"\n\t\tc.Mechanism = \"http\"\n\t}\n\n\t// Actually intercepting something\n\tif c.AgentName == \"\" {\n\t\tc.AgentName = c.Name\n\t}\n\tif len(c.Ports) == 0 {\n\t\t// Port defaults to the targeted container port unless a default is explicitly set in the client config.\n\t\tif dp := client.GetConfig(cmd.Context()).Intercept().DefaultPort; dp != 0 {\n\t\t\tc.Ports = []string{strconv.Itoa(dp)}\n\t\t}\n\t}\n\tif err := c.MountFlags.Validate(cmd); err != nil {\n\t\treturn err\n\t}\n\tif c.DockerFlags.Mount != \"\" && !c.MountFlags.Enabled {\n\t\treturn errors.New(\"--docker-mount cannot be used with --mount=false\")\n\t}\n\tif c.Wiretap {\n\t\tc.MountFlags.ReadOnly = true\n\t}\n\tif c.FormattedOutput || c.EnvFlags.File == \"-\" {\n\t\t// Can't mix JSON or env output on stdout with progress monitor.\n\t\tglobal.SetProgressQuiet(cmd)\n\t}\n\terr := c.DockerFlags.Validate(c.Cmdline)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tclog.Debugf(cmd.Context(), \"Docker flags = %v\", c.DockerFlags)\n\treturn nil\n}\n\nfunc (c *Command) ValidateReplace(cmd *cobra.Command, positional []string) error {\n\tif len(positional) > 1 && cmd.Flags().ArgsLenAtDash() != 1 {\n\t\treturn errcat.User.New(\"commands to be run with replace must come after options\")\n\t}\n\tc.Name = positional[0]\n\tc.AgentName = c.Name\n\tc.Cmdline = positional[1:]\n\tc.FormattedOutput = output.WantsFormatted(cmd)\n\tc.Mechanism = \"tcp\"\n\tc.Replace = true\n\tc.NoDefaultPort = true\n\n\tif c.ContainerName != \"\" {\n\t\tc.Name += \"/\" + c.ContainerName\n\t}\n\n\tif err := c.MountFlags.Validate(cmd); err != nil {\n\t\treturn err\n\t}\n\tif c.DockerFlags.Mount != \"\" && !c.MountFlags.Enabled {\n\t\treturn errors.New(\"--docker-mount cannot be used with --mount=false\")\n\t}\n\tfor i := range c.Ports {\n\t\tif c.Ports[i] == \"all\" {\n\t\t\t// Local port is unset, remote port is \"all\"\n\t\t\tc.Ports[i] = \":all\"\n\t\t}\n\t}\n\treturn c.DockerFlags.Validate(c.Cmdline)\n}\n\nfunc (c *Command) Run(cmd *cobra.Command, positional []string) error {\n\terr := c.Validate(cmd, positional)\n\tif err == nil {\n\t\terr = c.validatedRun(cmd)\n\t}\n\treturn err\n}\n\nfunc (c *Command) RunReplace(cmd *cobra.Command, positional []string) error {\n\terr := c.ValidateReplace(cmd, positional)\n\tif err == nil {\n\t\terr = c.validatedRun(cmd)\n\t}\n\treturn err\n}\n\nfunc (c *Command) validatedRun(cmd *cobra.Command) error {\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\treturn err\n\t}\n\tdefer progress.Stop(cmd.Context())\n\tctx := dos.WithStdio(cmd.Context(), cmd)\n\t_, err := NewState(c, c.MountFlags.ValidateConnected(ctx)).Run(ctx)\n\treturn err\n}\n\nfunc autocompleteService(cmd *cobra.Command, args []string, toComplete string) (serviceNames []string, directive cobra.ShellCompDirective) {\n\tctx, s, err := connect.GetOptionalSession(cmd)\n\tif s == nil || err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tif len(args) == 0 {\n\t\tkc, err := daemon.GetCommandKubeConfig(cmd)\n\t\tif err != nil {\n\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t}\n\t\tki, err := kubernetes.NewForConfig(kc.RestConfig)\n\t\tif err != nil {\n\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t}\n\t\tsvcs, err := k8sapi.Services(k8sapi.WithK8sInterface(kc, ki), kc.Namespace, nil)\n\t\tif err != nil {\n\t\t\treturn nil, cobra.ShellCompDirectiveError\n\t\t}\n\t\tfor _, svc := range svcs {\n\t\t\tn := svc.GetName()\n\t\t\tif toComplete == \"\" || strings.HasPrefix(n, toComplete) {\n\t\t\t\tserviceNames = append(serviceNames, n)\n\t\t\t}\n\t\t}\n\t\treturn serviceNames, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace\n\t}\n\tsc, err := s.GetAgentConfig(ctx, args[0])\n\tif err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tcn := cmd.Flag(\"container\").Value.String()\n\tfor _, c := range sc.Containers {\n\t\tif cn != \"\" && cn != c.Name {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, ic := range c.Intercepts {\n\t\t\tn := ic.ServiceName\n\t\t\tif toComplete == \"\" || strings.HasPrefix(n, toComplete) {\n\t\t\t\tserviceNames = append(serviceNames, n)\n\t\t\t}\n\t\t}\n\t}\n\tsort.Strings(serviceNames)\n\treturn slices.Compact(serviceNames), cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace\n}\n\nfunc ValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {\n\t// Trace level is used here, because we generally don't want to log expansion attempts\n\t// in the cli.log\n\tclog.Tracef(cmd.Context(), \"toComplete = %s, args = %v\", toComplete, args)\n\n\tif len(args) > 0 {\n\t\tif slices.Contains(os.Args, \"--\") {\n\t\t\tif cmd.Flag(\"docker-run\").Changed {\n\t\t\t\treturn docker.AutocompleteRun(cmd, args[1:], toComplete)\n\t\t\t}\n\t\t\t// Scan for command to execute\n\t\t\treturn nil, cobra.ShellCompDirectiveDefault\n\t\t}\n\t\t// Not completing the name of the workload\n\t\treturn nil, cobra.ShellCompDirectiveNoFileComp\n\t}\n\tif err := connect.InitCommand(cmd); err != nil {\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\tctx := cmd.Context()\n\n\tr, err := daemon.MustGetUserClient(ctx).List(ctx, &connector.ListRequest{Filter: connector.ListRequest_UNSPECIFIED})\n\tif err != nil {\n\t\tclog.Debugf(ctx, \"unable to get list of interceptable workloads: %v\", err)\n\t\treturn nil, cobra.ShellCompDirectiveError\n\t}\n\n\tlist := make([]string, 0)\n\tfor _, w := range r.Workloads {\n\t\t// only suggest strings that start with the string were autocompleting\n\t\tif strings.HasPrefix(w.Name, toComplete) {\n\t\t\tlist = append(list, w.Name)\n\t\t}\n\t}\n\n\t// TODO(raphaelreyna): This list can be quite large (in the double digits of MB).\n\t// There probably exists a number that would be a good cutoff limit.\n\n\treturn list, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveNoSpace\n}\n\n// BuildPathFilters builds a list of path filters from the given arguments.\n// The filters are of the form:\n//   - :path-equal:<path>\n//   - :path-prefix:<path>\n//   - :path-regex:<path>\nfunc BuildPathFilters(equals, prefixes, regexps []string) []string {\n\t// Combine all path filters with their type prefixes\n\tallFilters := make([]string, len(equals)+len(prefixes)+len(regexps))\n\n\t// Add exact match filters\n\ti := 0\n\tfor _, path := range equals {\n\t\tallFilters[i] = matcher.PathEqual + path\n\t\ti++\n\t}\n\n\t// Add prefix match filters\n\tfor _, path := range prefixes {\n\t\tallFilters[i] = matcher.PathPrefix + path\n\t\ti++\n\t}\n\n\t// Add regex match filters\n\tfor _, path := range regexps {\n\t\tallFilters[i] = matcher.PathRegex + path\n\t\ti++\n\t}\n\treturn allFilters\n}\n"
  },
  {
    "path": "pkg/client/cli/intercept/command_test.go",
    "content": "package intercept\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nfunc TestCommand_Validate_HTTPIntercepts(t *testing.T) {\n\tt.Skip(\"Skipping tests that require full telepresence config setup\")\n\ttests := []struct {\n\t\tname        string\n\t\tcmd         *Command\n\t\texpectError bool\n\t\terrorMsg    string\n\t}{\n\t\t{\n\t\t\tname: \"valid HTTP intercept with headers\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters: []string{\"X-User-ID=dev123\", \"X-Environment=staging\"},\n\t\t\t\tMechanism:         \"tcp\", // Will be overridden to \"http\"\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"valid HTTP intercept with paths\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPPathPrefixFilters: []string{\"/api/v1/\", \"/admin/\"},\n\t\t\t\tMechanism:             \"tcp\", // Will be overridden to \"http\"\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"valid HTTP intercept with both headers and paths\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters:     []string{\"X-User-ID=dev123\"},\n\t\t\t\tHTTPPathPrefixFilters: []string{\"/api/\"},\n\t\t\t\tMechanism:             \"tcp\", // Will be overridden to \"http\"\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid header format\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters: []string{\"InvalidHeader\"},\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"invalid header format 'InvalidHeader': must be key=value or key: value\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty header key with equals\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters: []string{\"=value\"},\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"invalid header format '=value': key cannot be empty\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty header key with colon\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters: []string{\": value\"},\n\t\t\t},\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"invalid header format ': value': key cannot be empty\",\n\t\t},\n\t\t{\n\t\t\tname: \"valid header with colon separator (curl -H format)\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters: []string{\"X-User-ID: dev123\", \"Authorization: Bearer token\"},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"mixed header formats\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters: []string{\"X-User-ID=dev123\", \"Authorization: Bearer token\"},\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname: \"standard TCP intercept unchanged\",\n\t\t\tcmd: &Command{\n\t\t\t\tMechanism: \"tcp\",\n\t\t\t},\n\t\t\texpectError: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := &cobra.Command{}\n\t\t\t// Create a basic context for the test\n\t\t\tctx := cmd.Context()\n\t\t\tif ctx == nil {\n\t\t\t\tctx = context.Background()\n\t\t\t}\n\t\t\tcmd.SetContext(ctx)\n\n\t\t\t// Set up minimal required fields\n\t\t\tif tt.cmd.Mechanism == \"\" {\n\t\t\t\ttt.cmd.Mechanism = \"tcp\"\n\t\t\t}\n\n\t\t\terr := tt.cmd.Validate(cmd, []string{\"test-service\"})\n\n\t\t\tif tt.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), tt.errorMsg)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t// Verify mechanism is set to \"http\" when HTTP flags are used\n\t\t\t\tif tt.cmd.UsesHTTPMechanism() {\n\t\t\t\t\tassert.Equal(t, \"http\", tt.cmd.Mechanism)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCommand_UsesHTTPMechanism(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tcmd      *Command\n\t\texpected bool\n\t}{\n\t\t{\n\t\t\tname:     \"no HTTP flags\",\n\t\t\tcmd:      &Command{},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname: \"with HTTP header filters\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters: []string{\"X-User-ID=dev123\"},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname: \"with HTTP path filters\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPPathPrefixFilters: []string{\"/api/\"},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname: \"with both HTTP filters\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters:     []string{\"X-User-ID=dev123\"},\n\t\t\t\tHTTPPathPrefixFilters: []string{\"/api/\"},\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t{\n\t\t\tname: \"empty HTTP header filters\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPHeaderFilters: []string{},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tname: \"empty HTTP path filters\",\n\t\t\tcmd: &Command{\n\t\t\t\tHTTPPathEqualFilters: []string{},\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := tt.cmd.UsesHTTPMechanism()\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestParseHTTPHeader(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tinput       string\n\t\texpectedKey string\n\t\texpectedVal string\n\t\texpectError bool\n\t\terrorMsg    string\n\t}{\n\t\t{\n\t\t\tname:        \"equals separator\",\n\t\t\tinput:       \"X-User-ID=dev123\",\n\t\t\texpectedKey: \"X-User-ID\",\n\t\t\texpectedVal: \"dev123\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"colon separator\",\n\t\t\tinput:       \"X-User-ID: dev123\",\n\t\t\texpectedKey: \"X-User-ID\",\n\t\t\texpectedVal: \"dev123\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"colon separator with spaces\",\n\t\t\tinput:       \"Authorization: Bearer token=abc123\",\n\t\t\texpectedKey: \"Authorization\",\n\t\t\texpectedVal: \"Bearer token=abc123\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"equals in value\",\n\t\t\tinput:       \"Authorization=Bearer token=abc123\",\n\t\t\texpectedKey: \"Authorization\",\n\t\t\texpectedVal: \"Bearer token=abc123\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"extra spaces trimmed\",\n\t\t\tinput:       \"  X-User-ID  :  dev123  \",\n\t\t\texpectedKey: \"X-User-ID\",\n\t\t\texpectedVal: \"dev123\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid format no separator\",\n\t\t\tinput:       \"InvalidHeader\",\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"must be key=value or key: value\",\n\t\t},\n\t\t{\n\t\t\tname:        \"empty key with equals\",\n\t\t\tinput:       \"=value\",\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"must be key=value or key: value\",\n\t\t},\n\t\t{\n\t\t\tname:        \"empty key with colon\",\n\t\t\tinput:       \": value\",\n\t\t\texpectError: true,\n\t\t\terrorMsg:    \"must be key=value or key: value\",\n\t\t},\n\t\t{\n\t\t\tname:        \"empty value with equals\",\n\t\t\tinput:       \"Key=\",\n\t\t\texpectedKey: \"Key\",\n\t\t\texpectedVal: \"\",\n\t\t\texpectError: false,\n\t\t},\n\t\t{\n\t\t\tname:        \"empty value with colon\",\n\t\t\tinput:       \"Key:\",\n\t\t\texpectedKey: \"Key\",\n\t\t\texpectedVal: \"\",\n\t\t\texpectError: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tkey, value, err := parseKeyValue(tt.input)\n\n\t\t\tif tt.expectError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tassert.Contains(t, err.Error(), tt.errorMsg)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tt.expectedKey, key)\n\t\t\t\tassert.Equal(t, tt.expectedVal, value)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCommand_HeaderParsing(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\theaders  []string\n\t\texpected map[string]string\n\t}{\n\t\t{\n\t\t\tname:    \"single header\",\n\t\t\theaders: []string{\"X-User-ID=dev123\"},\n\t\t\texpected: map[string]string{\n\t\t\t\t\"X-User-ID\": \"dev123\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"multiple headers\",\n\t\t\theaders: []string{\"X-User-ID=dev123\", \"X-Environment=staging\"},\n\t\t\texpected: map[string]string{\n\t\t\t\t\"X-User-ID\":     \"dev123\",\n\t\t\t\t\"X-Environment\": \"staging\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:    \"header with equals in value\",\n\t\t\theaders: []string{\"Authorization=Bearer token=123\"},\n\t\t\texpected: map[string]string{\n\t\t\t\t\"Authorization\": \"Bearer token=123\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"no headers\",\n\t\t\theaders:  []string{},\n\t\t\texpected: map[string]string{},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// This test verifies the header parsing logic in state.go\n\t\t\tresult := make(map[string]string)\n\t\t\tfor _, header := range tt.headers {\n\t\t\t\tparts := strings.SplitN(header, \"=\", 2)\n\t\t\t\tif len(parts) == 2 {\n\t\t\t\t\tresult[parts[0]] = parts[1]\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestCommand_UDPHTTPValidation(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tports         []string\n\t\thasHTTPFilter bool\n\t\texpectError   bool\n\t}{\n\t\t{\n\t\t\tname:          \"TCP port with HTTP filters - should work\",\n\t\t\tports:         []string{\"8080\"},\n\t\t\thasHTTPFilter: true,\n\t\t\texpectError:   false,\n\t\t},\n\t\t{\n\t\t\tname:          \"TCP port explicit with HTTP filters - should work\",\n\t\t\tports:         []string{\"8080/TCP\"},\n\t\t\thasHTTPFilter: true,\n\t\t\texpectError:   false,\n\t\t},\n\t\t{\n\t\t\tname:          \"UDP port with HTTP filters - should fail\",\n\t\t\tports:         []string{\"8080/UDP\"},\n\t\t\thasHTTPFilter: true,\n\t\t\texpectError:   true,\n\t\t},\n\t\t{\n\t\t\tname:          \"UDP port without HTTP filters - should work\",\n\t\t\tports:         []string{\"8080/UDP\"},\n\t\t\thasHTTPFilter: false,\n\t\t\texpectError:   false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tcmd := &Command{Ports: tt.ports}\n\t\t\tif tt.hasHTTPFilter {\n\t\t\t\tcmd.HTTPHeaderFilters = []string{\"X-User-ID=test\"}\n\t\t\t}\n\n\t\t\tif tt.expectError {\n\t\t\t\t// Test the logic that would fail - UDP + HTTP filters\n\t\t\t\tfor _, portSpec := range cmd.Ports {\n\t\t\t\t\tif pp, err := types.ParsePortAndProto(portSpec); err == nil && pp.Proto == types.ProtoUDP {\n\t\t\t\t\t\tif cmd.UsesHTTPMechanism() {\n\t\t\t\t\t\t\t// This should be the error condition\n\t\t\t\t\t\t\tassert.True(t, true, \"Expected UDP+HTTP to be detected\")\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.Fail(t, \"Expected error condition not detected\")\n\t\t\t} else {\n\t\t\t\t// Test that non-error conditions work\n\t\t\t\thasUDPWithHTTP := false\n\t\t\t\tfor _, portSpec := range cmd.Ports {\n\t\t\t\t\tif pp, err := types.ParsePortAndProto(portSpec); err == nil && pp.Proto == types.ProtoUDP {\n\t\t\t\t\t\tif cmd.UsesHTTPMechanism() {\n\t\t\t\t\t\t\thasUDPWithHTTP = true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassert.False(t, hasUDPWithHTTP, \"Should not have UDP+HTTP combination\")\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/intercept/describe_intercepts.go",
    "content": "package intercept\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/ingest\"\n)\n\nfunc DescribeIntercepts(ctx context.Context, iis []*manager.InterceptInfo, igs []*rpc.IngestInfo, volumeMountsPrevented error, debug bool) string {\n\tsb := strings.Builder{}\n\tif len(iis) > 0 {\n\t\tvar nis, ris, wts []*manager.InterceptInfo\n\t\tfor _, ii := range iis {\n\t\t\tswitch {\n\t\t\tcase ii.Spec.NoDefaultPort:\n\t\t\t\tris = append(ris, ii)\n\t\t\tcase ii.Spec.Wiretap:\n\t\t\t\twts = append(wts, ii)\n\t\t\tdefault:\n\t\t\t\tnis = append(nis, ii)\n\t\t\t}\n\t\t}\n\t\tif len(ris) > 0 {\n\t\t\tsb.WriteString(\"replaced\")\n\t\t\tfor _, ii := range ris {\n\t\t\t\tsb.WriteByte('\\n')\n\t\t\t\tdescribeIntercept(ctx, ii, volumeMountsPrevented, debug, &sb)\n\t\t\t}\n\t\t}\n\t\tif len(nis) > 0 {\n\t\t\tsb.WriteString(\"intercepted\")\n\t\t\tfor _, ii := range nis {\n\t\t\t\tsb.WriteByte('\\n')\n\t\t\t\tdescribeIntercept(ctx, ii, volumeMountsPrevented, debug, &sb)\n\t\t\t}\n\t\t}\n\t\tif len(wts) > 0 {\n\t\t\tsb.WriteString(\"wiretapped\")\n\t\t\tfor _, ii := range wts {\n\t\t\t\tsb.WriteByte('\\n')\n\t\t\t\tdescribeIntercept(ctx, ii, volumeMountsPrevented, debug, &sb)\n\t\t\t}\n\t\t}\n\t}\n\tif len(igs) > 0 {\n\t\tsb.WriteString(\"ingested\")\n\t\tfor _, ig := range igs {\n\t\t\tsb.WriteByte('\\n')\n\t\t\tdescribeIngest(ctx, ig, volumeMountsPrevented, &sb)\n\t\t}\n\t}\n\treturn sb.String()\n}\n\nfunc describeIntercept(ctx context.Context, ii *manager.InterceptInfo, volumeMountsPrevented error, debug bool, sb *strings.Builder) {\n\tinfo := NewInfo(ctx, ii, false, volumeMountsPrevented)\n\tinfo.debug = debug\n\t_, _ = info.WriteTo(sb)\n}\n\nfunc describeIngest(ctx context.Context, ig *rpc.IngestInfo, volumeMountsPrevented error, sb *strings.Builder) {\n\tinfo := ingest.NewInfo(ctx, ig, volumeMountsPrevented)\n\t_, _ = info.WriteTo(sb)\n}\n"
  },
  {
    "path": "pkg/client/cli/intercept/info.go",
    "content": "package intercept\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"strings\"\n\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/mount\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/matcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype Ingress struct {\n\tHost   string `json:\"host,omitempty\"    yaml:\"host,omitempty\"`\n\tPort   int32  `json:\"port,omitempty\"    yaml:\"port,omitempty\"`\n\tUseTLS bool   `json:\"use_tls,omitempty\" yaml:\"use_tls,omitempty\"`\n\tL5Host string `json:\"l5host,omitempty\"  yaml:\"l5host,omitempty\"`\n}\n\ntype Info struct {\n\tID            string            `json:\"id,omitempty\"              yaml:\"id,omitempty\"`\n\tName          string            `json:\"name,omitempty\"            yaml:\"name,omitempty\"`\n\tDisposition   string            `json:\"disposition,omitempty\"     yaml:\"disposition,omitempty\"`\n\tMessage       string            `json:\"message,omitempty\"         yaml:\"message,omitempty\"`\n\tWorkloadKind  string            `json:\"workload_kind,omitempty\"   yaml:\"workload_kind,omitempty\"`\n\tTargetHost    string            `json:\"target_host,omitempty\"     yaml:\"target_host,omitempty\"`\n\tTargetPort    int32             `json:\"target_port,omitempty\"     yaml:\"target_port,omitempty\"`\n\tPodPorts      []string          `json:\"pod_ports,omitempty\"       yaml:\"pod_ports,omitempty\"`\n\tServiceUID    string            `json:\"service_uid,omitempty\"     yaml:\"service_uid,omitempty\"`\n\tServicePortID string            `json:\"service_port_id,omitempty\" yaml:\"service_port_id,omitempty\"` // ServicePortID is deprecated. Use PortID\n\tPortID        string            `json:\"port_id,omitempty\"         yaml:\"port_id,omitempty\"`\n\tContainerName string            `json:\"container_name,omitempty\"  yaml:\"container_name,omitempty\"`\n\tContainerPort int32             `json:\"container_port,omitempty\"  yaml:\"container_port,omitempty\"`\n\tProtocol      types.Proto       `json:\"protocol,omitempty\"        yaml:\"protocol,omitempty\"`\n\tEnvironment   map[string]string `json:\"environment,omitempty\"     yaml:\"environment,omitempty\"`\n\tMount         *mount.Info       `json:\"mount,omitempty\"           yaml:\"mount,omitempty\"`\n\tFilterDesc    string            `json:\"filter_desc,omitempty\"     yaml:\"filter_desc,omitempty\"`\n\tMetadata      map[string]string `json:\"metadata,omitempty\"        yaml:\"metadata,omitempty\"`\n\tHeaderFilters map[string]string `json:\"header_filters,omitempty\"  yaml:\"header_filters,omitempty\"`\n\tPathFilters   []string          `json:\"path_filters,omitempty\"    yaml:\"path_filters,omitempty\"`\n\tGlobal        bool              `json:\"global,omitempty\"          yaml:\"global,omitempty\"`\n\tReplace       bool              `json:\"replace,omitempty\"         yaml:\"replace,omitempty\"`\n\tWiretap       bool              `json:\"wiretap,omitempty\"         yaml:\"wiretap,omitempty\"`\n\tPodIP         string            `json:\"pod_ip,omitempty\"          yaml:\"pod_ip,omitempty\"`\n\tdebug         bool\n}\n\nfunc NewInfo(ctx context.Context, ii *manager.InterceptInfo, ro bool, mountError error) *Info {\n\tspec := ii.Spec\n\tvar m *mount.Info\n\tif mountError != nil {\n\t\tm = &mount.Info{Error: mountError.Error()}\n\t} else if ii.MountPoint != \"\" {\n\t\tm = mount.NewInfo(ctx,\n\t\t\tii.Environment, uint16(ii.FtpPort), uint16(ii.SftpPort), ii.ClientMountPoint, ii.MountPoint, ii.PodIp, types.MountPoliciesFromRPC(ii.Mounts), ro)\n\t}\n\tinfo := &Info{\n\t\tID:            ii.Id,\n\t\tName:          spec.Name,\n\t\tDisposition:   ii.Disposition.String(),\n\t\tMessage:       ii.Message,\n\t\tWorkloadKind:  spec.WorkloadKind,\n\t\tTargetHost:    spec.TargetHost,\n\t\tTargetPort:    spec.TargetPort,\n\t\tPodPorts:      spec.PodPorts,\n\t\tMount:         m,\n\t\tServiceUID:    spec.ServiceUid,\n\t\tPortID:        spec.PortIdentifier,\n\t\tContainerName: spec.ContainerName,\n\t\tContainerPort: spec.ContainerPort,\n\t\tProtocol:      types.FromK8sProtocol(core.Protocol(spec.Protocol)),\n\t\tPodIP:         ii.PodIp,\n\t\tEnvironment:   ii.Environment,\n\t\tFilterDesc:    ii.MechanismArgsDesc,\n\t\tMetadata:      spec.Metadata,\n\t\tHeaderFilters: spec.HeaderFilters,\n\t\tPathFilters:   spec.PathFilters,\n\t\tGlobal:        spec.Mechanism == \"tcp\",\n\t\tReplace:       spec.NoDefaultPort, // spec.Replace can't be used because it's set by deprecated --replace flag\n\t\tWiretap:       spec.Wiretap,\n\t}\n\n\t// Replace potentially synthetic TargetHost\n\ttargetIP, err := netip.ParseAddr(spec.TargetHost)\n\tif err == nil && targetIP.Is6() {\n\t\tif s := daemon.GetSession(ctx); s != nil {\n\t\t\tr, err := s.ResolveSyntheticIP(ctx, &connector.ResolveSyntheticRequest{\n\t\t\t\tIp:       targetIP.AsSlice(),\n\t\t\t\tNameOnly: true,\n\t\t\t})\n\t\t\tif err == nil {\n\t\t\t\tinfo.TargetHost = r.Name\n\t\t\t}\n\t\t}\n\t}\n\tif spec.ServiceUid != \"\" {\n\t\t// For backward compatibility in JSON output\n\t\tinfo.ServicePortID = info.PortID\n\t}\n\treturn info\n}\n\nfunc (ii *Info) String() string {\n\tsb := strings.Builder{}\n\t_, _ = ii.WriteTo(&sb)\n\treturn sb.String()\n}\n\nfunc (ii *Info) WriteTo(w io.Writer) (int64, error) {\n\tkvf := ioutil.DefaultKeyValueFormatter()\n\tkvf.Prefix = \"   \"\n\twhat := \"Intercepting\"\n\tswitch {\n\tcase ii.Replace:\n\t\tkvf.Add(\"Container name\", ii.ContainerName)\n\t\twhat = \"Port forwards\"\n\tcase ii.Wiretap:\n\t\tkvf.Add(\"Wiretap name\", ii.Name)\n\t\twhat = \"Wiretapping\"\n\tdefault:\n\t\tkvf.Add(\"Intercept name\", ii.Name)\n\t}\n\tkvf.Add(\"State\", func() string {\n\t\tmsg := \"\"\n\t\tif manager.InterceptDispositionType_value[ii.Disposition] > int32(manager.InterceptDispositionType_WAITING) {\n\t\t\tmsg += \"error: \"\n\t\t}\n\t\tmsg += ii.Disposition\n\t\tif ii.Message != \"\" {\n\t\t\tmsg += \": \" + ii.Message\n\t\t}\n\t\treturn msg\n\t}())\n\tkvf.Add(\"Workload kind\", ii.WorkloadKind)\n\n\tif ii.debug {\n\t\tkvf.Add(\"ID\", ii.ID)\n\t}\n\n\t// Show all ports as mappings from containter port to local port.\n\tpkv := ioutil.DefaultKeyValueFormatter()\n\tpkv.Indent = \"\"\n\tpkv.Separator = \" -> \"\n\tif ii.ContainerPort != 0 {\n\t\tpm, _ := types.NewPortIdentifier(ii.Protocol, strconv.Itoa(int(ii.ContainerPort)))\n\t\tpkv.Add(pm.String(), fmt.Sprintf(\"%d %s\", ii.TargetPort, ii.Protocol))\n\t}\n\tfor _, pp := range ii.PodPorts {\n\t\tpm := types.PortMapping(pp)\n\t\tto := pm.ToAsNumeric()\n\t\tpkv.Add(pm.From().String(), fmt.Sprintf(\"%d %s\", to.Port, to.Proto))\n\t}\n\tkvf.Add(what, fmt.Sprintf(\"%s -> %s\\n%s\", ii.PodIP, ii.TargetHost, pkv))\n\n\tif !ii.Global {\n\t\tkvf.Add(what, func() string {\n\t\t\tif ii.FilterDesc != \"\" {\n\t\t\t\treturn ii.FilterDesc\n\t\t\t}\n\t\t\treturn matcher.NewRequest(ii.PathFilters, ii.HeaderFilters).String()\n\t\t}())\n\t}\n\n\tif m := ii.Mount; m != nil {\n\t\tif m.LocalDir != \"\" {\n\t\t\tkvf.Add(\"Volume Mount Point\", m.LocalDir)\n\t\t} else if m.Error != \"\" {\n\t\t\tkvf.Add(\"Volume Mount Error\", m.Error)\n\t\t}\n\t}\n\n\tif len(ii.Metadata) > 0 {\n\t\tkvf.Add(\"Metadata\", fmt.Sprintf(\"%q\", ii.Metadata))\n\t}\n\treturn kvf.WriteTo(w)\n}\n"
  },
  {
    "path": "pkg/client/cli/intercept/info_test.go",
    "content": "package intercept\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tv1 \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n)\n\nfunc TestInfo_HTTPFilterDisplay(t *testing.T) {\n\ttests := []struct {\n\t\tname            string\n\t\tspec            *manager.InterceptSpec\n\t\tinterceptInfo   *manager.InterceptInfo\n\t\texpectedPattern string\n\t\tdescription     string\n\t}{\n\t\t{\n\t\t\tname: \"TCP intercept does not show filter section\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-tcp\",\n\t\t\t\tMechanism: \"tcp\",\n\t\t\t},\n\t\t\tinterceptInfo: &manager.InterceptInfo{\n\t\t\t\tId:                \"test-id\",\n\t\t\t\tDisposition:       manager.InterceptDispositionType_ACTIVE,\n\t\t\t\tPodIp:             \"10.0.0.1\",\n\t\t\t\tMechanismArgsDesc: \"all TCP connections\",\n\t\t\t},\n\t\t\texpectedPattern: \"Intercept name\",\n\t\t\tdescription:     \"TCP intercepts should not show HTTP filter section\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP intercept with header filters\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-http\",\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-User-ID\":     \"dev123\",\n\t\t\t\t\t\"X-Environment\": \"staging\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterceptInfo: &manager.InterceptInfo{\n\t\t\t\tId:          \"test-id\",\n\t\t\t\tDisposition: manager.InterceptDispositionType_ACTIVE,\n\t\t\t\tPodIp:       \"10.0.0.1\",\n\t\t\t},\n\t\t\texpectedPattern: \"HTTP requests with headers\",\n\t\t\tdescription:     \"HTTP intercepts should show header filters\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP intercept with path filters\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-http\",\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tPathFilters: []string{\n\t\t\t\t\t\":path-regex:/api/v1/.*\",\n\t\t\t\t\t\":path-regex:/admin/.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterceptInfo: &manager.InterceptInfo{\n\t\t\t\tId:          \"test-id\",\n\t\t\t\tDisposition: manager.InterceptDispositionType_ACTIVE,\n\t\t\t\tPodIp:       \"10.0.0.1\",\n\t\t\t},\n\t\t\texpectedPattern: `(?m:HTTP requests with paths\\n\\s+=~ /api/v1/\\.\\*\\n\\s+=~ /admin/\\.\\*)`,\n\t\t\tdescription:     \"HTTP intercepts should show path filters\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP intercept with both header and path filters\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-http\",\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-User-ID\": \"dev123\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\n\t\t\t\t\t\":path-regex:/api/.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterceptInfo: &manager.InterceptInfo{\n\t\t\t\tId:          \"test-id\",\n\t\t\t\tDisposition: manager.InterceptDispositionType_ACTIVE,\n\t\t\t\tPodIp:       \"10.0.0.1\",\n\t\t\t},\n\t\t\texpectedPattern: `HTTP requests with path =~ /api/\\.\\* and header 'X-User-Id: dev123'`,\n\t\t\tdescription:     \"HTTP intercepts should show both header and path filters\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP intercept with no filters\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-http\",\n\t\t\t\tMechanism: \"http\",\n\t\t\t},\n\t\t\tinterceptInfo: &manager.InterceptInfo{\n\t\t\t\tId:          \"test-id\",\n\t\t\t\tDisposition: manager.InterceptDispositionType_ACTIVE,\n\t\t\t\tPodIp:       \"10.0.0.1\",\n\t\t\t},\n\t\t\texpectedPattern: \"all TCP connections\",\n\t\t\tdescription:     \"HTTP intercepts with no filters should show 'all HTTP connections'\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP intercept with FilterDesc override\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-http\",\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-User-ID\": \"dev123\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tinterceptInfo: &manager.InterceptInfo{\n\t\t\t\tId:                \"test-id\",\n\t\t\t\tDisposition:       manager.InterceptDispositionType_ACTIVE,\n\t\t\t\tPodIp:             \"10.0.0.1\",\n\t\t\t\tMechanismArgsDesc: \"custom description\",\n\t\t\t},\n\t\t\texpectedPattern: \"custom description\",\n\t\t\tdescription:     \"FilterDesc should override generated HTTP filter description\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Set up minimal required fields\n\t\t\ttt.spec.TargetHost = \"127.0.0.1\"\n\t\t\ttt.spec.TargetPort = 8080\n\t\t\ttt.spec.Protocol = string(v1.ProtocolTCP)\n\t\t\ttt.interceptInfo.Spec = tt.spec\n\n\t\t\t// Create Info object\n\t\t\tinfo := NewInfo(context.Background(), tt.interceptInfo, false, nil)\n\n\t\t\t// Get the string representation\n\t\t\toutput := info.String()\n\n\t\t\t// Check if the expected pattern is in the output\n\t\t\tassert.Regexp(t, tt.expectedPattern, output, tt.description)\n\n\t\t\t// Special handling for header filter test - check individual headers due to map iteration order\n\t\t\tif tt.name == \"HTTP intercept with header filters\" {\n\t\t\t\tassert.Contains(t, output, \"'X-User-Id: dev123'\", \"Should contain X-User-ID header\")\n\t\t\t\tassert.Contains(t, output, \"'X-Environment: staging'\", \"Should contain X-Environment header\")\n\t\t\t}\n\n\t\t\t// Verify that TCP intercepts don't show filter info (Global should be true)\n\t\t\tif tt.spec.Mechanism == \"tcp\" {\n\t\t\t\tassert.True(t, info.Global, \"TCP intercepts should have Global=true\")\n\t\t\t\t// Should NOT contain \"HTTP filters:\" for TCP intercepts\n\t\t\t\tassert.NotContains(t, output, \"HTTP requests\", \"TCP intercepts should not show HTTP filter info\")\n\t\t\t} else {\n\t\t\t\tassert.False(t, info.Global, \"HTTP intercepts should have Global=false\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewInfo_HTTPFilterPopulation(t *testing.T) {\n\ttests := []struct {\n\t\tname                string\n\t\tspec                *manager.InterceptSpec\n\t\texpectedHeaderCount int\n\t\texpectedPathCount   int\n\t\texpectedGlobal      bool\n\t}{\n\t\t{\n\t\t\tname: \"TCP intercept\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-tcp\",\n\t\t\t\tMechanism: \"tcp\",\n\t\t\t},\n\t\t\texpectedHeaderCount: 0,\n\t\t\texpectedPathCount:   0,\n\t\t\texpectedGlobal:      true,\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP intercept with filters\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-http\",\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-User-ID\":     \"dev123\",\n\t\t\t\t\t\"X-Environment\": \"staging\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\n\t\t\t\t\t\"/api/v1/*\",\n\t\t\t\t\t\"/admin/*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpectedHeaderCount: 2,\n\t\t\texpectedPathCount:   2,\n\t\t\texpectedGlobal:      false,\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP intercept with no filters\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tName:      \"test-http\",\n\t\t\t\tMechanism: \"http\",\n\t\t\t},\n\t\t\texpectedHeaderCount: 0,\n\t\t\texpectedPathCount:   0,\n\t\t\texpectedGlobal:      false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\t// Set up minimal required fields\n\t\t\ttt.spec.TargetHost = \"127.0.0.1\"\n\t\t\ttt.spec.TargetPort = 8080\n\t\t\ttt.spec.Protocol = string(v1.ProtocolTCP)\n\n\t\t\tinterceptInfo := &manager.InterceptInfo{\n\t\t\t\tId:          \"test-id\",\n\t\t\t\tDisposition: manager.InterceptDispositionType_ACTIVE,\n\t\t\t\tPodIp:       \"10.0.0.1\",\n\t\t\t\tSpec:        tt.spec,\n\t\t\t}\n\n\t\t\t// Create Info object\n\t\t\tinfo := NewInfo(context.Background(), interceptInfo, false, nil)\n\n\t\t\t// Verify field population\n\t\t\tassert.Len(t, info.HeaderFilters, tt.expectedHeaderCount, \"Header filters should be populated correctly\")\n\t\t\tassert.Len(t, info.PathFilters, tt.expectedPathCount, \"Path filters should be populated correctly\")\n\t\t\tassert.Equal(t, tt.expectedGlobal, info.Global, \"Global flag should be set correctly\")\n\n\t\t\t// Verify header filter content\n\t\t\tif len(tt.spec.HeaderFilters) > 0 {\n\t\t\t\tfor key, value := range tt.spec.HeaderFilters {\n\t\t\t\t\tassert.Equal(t, value, info.HeaderFilters[key], \"Header filter values should match\")\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Verify path filter content\n\t\t\tif len(tt.spec.PathFilters) > 0 {\n\t\t\t\tassert.Equal(t, tt.spec.PathFilters, info.PathFilters, \"Path filters should match\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestInfo_WriteTo_HTTPFilterFormatting(t *testing.T) {\n\t// Test specific formatting of HTTP filters in WriteTo output\n\tspec := &manager.InterceptSpec{\n\t\tName:       \"test-service\",\n\t\tMechanism:  \"http\",\n\t\tTargetHost: \"127.0.0.1\",\n\t\tTargetPort: 8080,\n\t\tProtocol:   string(v1.ProtocolTCP),\n\t\tHeaderFilters: map[string]string{\n\t\t\t\"X-User-ID\":     \"dev123\",\n\t\t\t\"Authorization\": \"Bearer token=abc123\",\n\t\t},\n\t\tPathFilters: []string{\n\t\t\t\":path-regex:/api/v1/.*\",\n\t\t\t\":path-equal:/health\",\n\t\t},\n\t}\n\n\tinterceptInfo := &manager.InterceptInfo{\n\t\tId:          \"test-id\",\n\t\tDisposition: manager.InterceptDispositionType_ACTIVE,\n\t\tPodIp:       \"10.0.0.1\",\n\t\tSpec:        spec,\n\t}\n\n\tinfo := NewInfo(context.Background(), interceptInfo, false, nil)\n\toutput := info.String()\n\n\t// Should contain HTTP filters section\n\tassert.Contains(t, output, \"HTTP requests\", \"Output should contain HTTP requests label\")\n\n\t// Should contain all header filters (order may vary due to map iteration)\n\tassert.Contains(t, output, \"'X-User-Id: dev123'\", \"Output should contain X-User-ID header filter\")\n\tassert.Contains(t, output, \"'Authorization: Bearer token=abc123'\", \"Output should contain Authorization header filter with equals in value\")\n\n\t// Should contain all path filters\n\tassert.Contains(t, output, \"=~ /api/v1/.*\", \"Output should contain API path filter\")\n\tassert.Contains(t, output, \"== /health\", \"Output should contain health path filter\")\n\n\t// Should not show \"all TCP connections\" for HTTP intercepts\n\tassert.NotContains(t, output, \"all TCP connections\", \"HTTP intercepts should not show TCP connection message\")\n}\n"
  },
  {
    "path": "pkg/client/cli/intercept/state.go",
    "content": "package intercept\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strings\"\n\n\tgrpcCodes \"google.golang.org/grpc/codes\"\n\tgrpcStatus \"google.golang.org/grpc/status\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\tcliDocker \"github.com/telepresenceio/telepresence/v2/pkg/client/cli/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype State interface {\n\tCreateRequest(context.Context) (*connector.CreateInterceptRequest, error)\n\tName() string\n\tRun(context.Context) (*Info, error)\n\tRunAndLeave() bool\n}\n\ntype state struct {\n\t*Command\n\tenv              map[string]string\n\tlocalPort        uint16 // the parsed <local port>\n\tdockerPort       uint16\n\tstatus           *connector.ConnectInfo\n\tinfo             *Info // Info from the created intercept\n\tmountError       error\n\thandlerContainer string\n\n\t// Possibly extended version of the state. Use when calling interface methods.\n\tself State\n}\n\nfunc NewState(\n\targs *Command,\n\tmountError error,\n) State {\n\ts := &state{\n\t\tCommand:    args,\n\t\tmountError: mountError,\n\t}\n\ts.self = s\n\treturn s\n}\n\nfunc (s *state) SetSelf(self State) {\n\ts.self = self\n}\n\nfunc keyValueMap(keyValueStrings []string) (m map[string]string) {\n\tif l := len(keyValueStrings); l > 0 {\n\t\tm = make(map[string]string, l)\n\t\tfor _, kv := range keyValueStrings {\n\t\t\tif key, value, err := parseKeyValue(kv); err == nil {\n\t\t\t\tm[key] = value\n\t\t\t}\n\t\t}\n\t}\n\treturn m\n}\n\nfunc (s *state) CreateRequest(ctx context.Context) (*connector.CreateInterceptRequest, error) {\n\tspec := &manager.InterceptSpec{\n\t\tName:          s.Name(),\n\t\tReplace:       s.Replace,\n\t\tServiceName:   s.ServiceName,\n\t\tContainerName: s.ContainerName,\n\t\tMechanism:     s.Mechanism,\n\t\tMetadata:      keyValueMap(s.Metadata),\n\t\tHeaderFilters: keyValueMap(s.HTTPHeaderFilters),\n\t\tPlaintext:     s.Plaintext,\n\t\tWiretap:       s.Wiretap,\n\t\tAgent:         s.AgentName,\n\t\tNoDefaultPort: s.NoDefaultPort,\n\t\tPathFilters:   BuildPathFilters(s.HTTPPathEqualFilters, s.HTTPPathPrefixFilters, s.HTTPPathRegexFilters),\n\t}\n\tir := &connector.CreateInterceptRequest{\n\t\tSpec:           spec,\n\t\tExtendedInfo:   s.ExtendedInfo,\n\t\tLocalMountPort: int32(s.MountFlags.LocalMountPort),\n\t\tMountPoint:     s.MountFlags.Mount,\n\t\tMountReadOnly:  s.MountFlags.ReadOnly,\n\t}\n\n\tfor _, toPod := range s.ToPod {\n\t\tpp, err := types.ParsePortAndProto(toPod)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tspec.LocalPorts = append(spec.LocalPorts, pp.String())\n\t}\n\n\tud := daemon.MustGetUserClient(ctx)\n\n\t// Parse port into spec based on how it's formatted\n\ts.localPort, s.dockerPort, spec.PortIdentifier = 0, 0, \"\"\n\tif len(s.Ports) > 0 {\n\t\tvar err error\n\t\ts.localPort, s.dockerPort, spec.PortIdentifier, err = parsePort(s.Ports[0], s.DockerFlags.Run, ud.Containerized())\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor i := 1; i < len(s.Ports); i++ {\n\t\t\tpm := s.Ports[i]\n\t\t\tif colIdx := strings.IndexByte(pm, ':'); colIdx > 0 {\n\t\t\t\t// The \"--port\" arg puts the local port first, but it's the destination in the pod-port mapping.\n\t\t\t\tto := pm[:colIdx]\n\t\t\t\tfrom := pm[colIdx+1:]\n\t\t\t\tif slashIdx := strings.IndexByte(from, '/'); slashIdx > 0 {\n\t\t\t\t\tfrom = from[:slashIdx]\n\t\t\t\t\tto += from[slashIdx:]\n\t\t\t\t}\n\t\t\t\tpm = from + \":\" + to\n\t\t\t}\n\t\t\tif err = types.PortMapping(pm).Validate(); err != nil {\n\t\t\t\treturn nil, errcat.User.New(err)\n\t\t\t}\n\t\t\tspec.PodPorts = append(spec.PodPorts, pm)\n\t\t}\n\t}\n\n\tspec.TargetPort = int32(s.localPort)\n\tswitch {\n\tcase s.Address != \"\":\n\t\tspec.TargetHost = s.Address\n\tcase ud.Containerized() && s.handlerContainer != \"\":\n\t\t// The name will be translated into a synthetic IP that will be registered with\n\t\t// the daemon. The daemon will reverse the translation when dialing the local\n\t\t// target and use DNS to find the container IP.\n\t\tspec.TargetHost = s.handlerContainer\n\tdefault:\n\t\tspec.TargetHost = \"127.0.0.1\"\n\t}\n\treturn ir, nil\n}\n\nfunc (s *state) Name() string {\n\treturn s.Command.Name\n}\n\nfunc (s *state) RunAndLeave() bool {\n\treturn len(s.Cmdline) > 0 || s.DockerFlags.Run\n}\n\nfunc (s *state) Run(ctx context.Context) (*Info, error) {\n\tprogress.Start(ctx, \"Initializing\")\n\tdefer progress.Stop(ctx)\n\n\tvar err error\n\tif !s.RunAndLeave() {\n\t\terr = client.WithEnsuredState(ctx, s.create, nil, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn s.info, nil\n\t}\n\n\t// start intercept, run command, then leave the intercept\n\tif s.DockerFlags.Run {\n\t\terr = s.DockerFlags.PullOrBuildImage(progress.WithEventId(ctx, \"Handler\"))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefaultContainerName := fmt.Sprintf(\"%s-%s-%d\", s.what(), s.Name(), s.localPort)\n\t\ts.handlerContainer, s.Cmdline, err = s.DockerFlags.GetContainerNameAndArgs(defaultContainerName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif s.handlerContainer != defaultContainerName {\n\t\t\t// Check if the given name is already in use.\n\t\t\tud := daemon.MustGetSession(ctx)\n\t\t\tip, err := ud.Lookup(ctx, s.handlerContainer)\n\t\t\tif err == nil {\n\t\t\t\t// We're about to start a container with a name that is already present in the cluster. That's\n\t\t\t\t// probably a mistake.\n\t\t\t\tprogress.Warningf(ctx, \"the container name %q will override the current mapping to IP %s\", s.handlerContainer, ip)\n\t\t\t}\n\t\t}\n\t}\n\terr = client.WithEnsuredState(ctx, s.create, s.runCommand, s.leave)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn s.info, nil\n}\n\nfunc (s *state) what() string {\n\twhat := \"intercept\"\n\tif s.Wiretap {\n\t\twhat = \"wiretap\"\n\t} else if s.NoDefaultPort {\n\t\twhat = \"replace\"\n\t}\n\treturn what\n}\n\nfunc (s *state) create(ctx context.Context) (acquired bool, err error) {\n\tud := daemon.MustGetUserClient(ctx)\n\ts.status, err = ud.Status(ctx, &empty.Empty{})\n\tif err != nil {\n\t\treturn false, grpc.FromGRPC(err)\n\t}\n\n\tprogress.Start(ctx, \"Creating\")\n\tdefer progress.Stop(ctx)\n\n\tir, err := s.self.CreateRequest(ctx)\n\tif err != nil {\n\t\treturn false, errcat.NoDaemonLogs.New(err)\n\t}\n\n\t// Submit the request\n\tegType := types.EngagementTypeFromSpec(ir.Spec)\n\tprogress.Working(ctx, egType.Working())\n\tintercept, err := ud.CreateIntercept(ctx, ir)\n\tif err = grpc.FromGRPC(err); err != nil {\n\t\treturn false, progress.MaybeWriteError(ctx, fmt.Errorf(\"connector.CreateIntercept: %w\", err))\n\t}\n\tprogress.Done(ctx, egType.WorkDone())\n\tprogress.Infof(ctx, \"Using %s %s\", intercept.Spec.WorkloadKind, s.AgentName)\n\n\ts.env = intercept.Environment\n\tif s.env == nil {\n\t\ts.env = make(map[string]string)\n\t}\n\ts.env[\"TELEPRESENCE_INTERCEPT_ID\"] = intercept.Id\n\ts.env[agentconfig.EnvAPIHost] = ud.DaemonID().ContainerName()\n\ts.env[\"TELEPRESENCE_ROOT\"] = intercept.ClientMountPoint\n\tif err = s.EnvFlags.MaybeWrite(s.env); err != nil {\n\t\treturn true, err\n\t}\n\n\tif s.MountFlags.Enabled {\n\t\tif ir.LocalMountPort != 0 {\n\t\t\tintercept.PodIp = \"127.0.0.1\"\n\t\t\tintercept.SftpPort = ir.LocalMountPort\n\t\t}\n\t} else {\n\t\tintercept.MountPoint = \"\"\n\t\tintercept.FtpPort = 0\n\t\tintercept.SftpPort = 0\n\t}\n\n\ts.info = NewInfo(ctx, intercept, s.MountFlags.ReadOnly, s.mountError)\n\tdetailedOutput := s.DetailedOutput && s.FormattedOutput\n\tif detailedOutput {\n\t\toutput.Object(ctx, s.info, true)\n\t} else {\n\t\tprogress.Info(ctx, s.info)\n\t}\n\treturn true, nil\n}\n\nfunc (s *state) leave(ctx context.Context) error {\n\tprogress.Start(ctx, \"Leaving\")\n\tm := s.info.Mount\n\tif m != nil && m.LocalDir != \"\" {\n\t\tdefer func() {\n\t\t\tif runtime.GOOS != \"windows\" {\n\t\t\t\t// remove if empty\n\t\t\t\t_ = os.Remove(m.LocalDir)\n\t\t\t}\n\t\t}()\n\t}\n\tn := strings.TrimSpace(s.Name())\n\tud := daemon.MustGetUserClient(ctx)\n\tprogress.Workingf(ctx, \"Ending %s\", s.what())\n\t_, err := ud.RemoveIntercept(ctx, &manager.RemoveInterceptRequest2{Name: n})\n\tif err != nil && grpcStatus.Code(err) == grpcCodes.Canceled {\n\t\t// Deactivation was caused by a disconnect\n\t\terr = nil\n\t}\n\tif err != nil {\n\t\terr = grpc.FromGRPC(err)\n\t\terr = progress.MaybeWriteError(ctx, err)\n\t} else {\n\t\tprogress.Donef(ctx, \"Ended %s\", s.what())\n\t}\n\treturn err\n}\n\nfunc (s *state) runCommand(ctx context.Context) error {\n\t// start the interceptor process\n\tprogress.Start(ctx, \"Starting\")\n\tdefer progress.Stop(ctx)\n\n\tif !s.DockerFlags.Run {\n\t\tenv := s.info.Environment\n\t\tcmd, err := proc.Start(ctx, env, s.Cmdline[0], s.Cmdline[1:]...)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"error interceptor starting process: %v\", err)\n\t\t\treturn errcat.NoDaemonLogs.New(err)\n\t\t}\n\t\tif err = daemon.MustGetUserClient(ctx).AddHandler(ctx, env[\"TELEPRESENCE_INTERCEPT_ID\"], cmd, \"\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// The external command will not output anything to the logs. An error here\n\t\t// is likely caused by the user hitting <ctrl>-C to terminate the process.\n\t\treturn errcat.NoDaemonLogs.New(proc.Wait(ctx, func() {}, cmd))\n\t}\n\n\tdr := cliDocker.Runner{\n\t\tFlags:         s.DockerFlags,\n\t\tContainerName: s.handlerContainer,\n\t\tEnvironment:   s.info.Environment,\n\t\tMount:         s.info.Mount,\n\t}\n\tif s.dockerPort != 0 {\n\t\ts.Cmdline = slices.Insert(s.Cmdline, 0, \"-p\", fmt.Sprintf(\"%d:%d\", s.localPort, s.dockerPort))\n\t\tdr.AdjustImageIndex(2)\n\t}\n\treturn dr.Run(ctx, s.WaitMessage, s.Cmdline...)\n}\n\n// parsePort parses portSpec based on how it's formatted.\nfunc parsePort(portSpec string, dockerRun, containerized bool) (local uint16, docker uint16, svcPortId string, err error) {\n\tif portSpec == \"\" {\n\t\treturn 0, 0, \"\", nil\n\t}\n\tportMapping := strings.Split(portSpec, \":\")\n\tportError := func() (uint16, uint16, string, error) {\n\t\tif dockerRun && !containerized {\n\t\t\treturn 0, 0, \"\", errcat.User.Newf(\"port must be of the format --port <local-port>:<container-port>[:<svcPortIdentifier>], was %q\", portSpec)\n\t\t}\n\t\treturn 0, 0, \"\", errcat.User.Newf(\"port must be of the format --port <local-port>[:<svcPortIdentifier>], was %q\", portSpec)\n\t}\n\n\tif p := portMapping[0]; p != \"\" {\n\t\tif local, err = types.ParsePort(p); err != nil {\n\t\t\treturn portError()\n\t\t}\n\t}\n\n\tswitch len(portMapping) {\n\tcase 1:\n\tcase 2:\n\t\tif p := portMapping[1]; p != \"\" {\n\t\t\tif p == \"all\" {\n\t\t\t\treturn 0, 0, p, nil\n\t\t\t}\n\t\t\tif dockerRun && !containerized {\n\t\t\t\tif docker, err = types.ParsePort(p); err != nil {\n\t\t\t\t\treturn portError()\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := types.ValidatePort(p); err != nil {\n\t\t\t\t\treturn portError()\n\t\t\t\t}\n\t\t\t\tsvcPortId = p\n\t\t\t}\n\t\t}\n\tcase 3:\n\t\tif containerized && dockerRun {\n\t\t\treturn 0, 0, \"\", errcat.User.New(\n\t\t\t\t\"the format --port <local-port>:<container-port>:<svcPortIdentifier> cannot be used when the daemon runs in a container\")\n\t\t}\n\t\tif !dockerRun {\n\t\t\treturn portError()\n\t\t}\n\t\tif docker, err = types.ParsePort(portMapping[1]); err != nil {\n\t\t\treturn portError()\n\t\t}\n\t\tsvcPortId = portMapping[2]\n\t\tif err := types.ValidatePort(svcPortId); err != nil {\n\t\t\treturn portError()\n\t\t}\n\tdefault:\n\t\treturn portError()\n\t}\n\tif dockerRun && !containerized && docker == 0 {\n\t\tdocker = local\n\t}\n\treturn local, docker, svcPortId, nil\n}\n"
  },
  {
    "path": "pkg/client/cli/main.go",
    "content": "package cli\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"os/exec\"\n\t\"slices\"\n\n\t\"github.com/spf13/cobra\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/cmd\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nfunc InitContext(ctx context.Context) context.Context {\n\tenv, err := client.LoadEnv()\n\tif err != nil {\n\t\tioutil.Printf(os.Stderr, \"Failed to load environment: %v\", err)\n\t\tos.Exit(1)\n\t}\n\tctx = client.WithEnv(ctx, &env)\n\tswitch client.ProcessName() {\n\tcase client.UserDaemonName:\n\t\tclient.DisplayName = \"OSS User Daemon\"\n\t\tif proc.RunningInContainer() {\n\t\t\tif slices.Contains(os.Args, \"--embed-network\") {\n\t\t\t\tclient.DisplayName = \"OSS Daemon in container\"\n\t\t\t} else {\n\t\t\t\t// False positive, likely due to a /.dockerenv file in CodesSpace\n\t\t\t\tproc.SetRunningInContainer(false)\n\t\t\t}\n\t\t}\n\tcase client.RootDaemonName:\n\t\tclient.DisplayName = \"OSS Root Daemon\"\n\t\tproc.SetRunningInContainer(false) // We never start the root daemon as a container.\n\tdefault:\n\t\tclient.DisplayName = \"OSS Client\"\n\t\tctx = docker.EnableClient(ctx)\n\t}\n\tif client.IsDaemon() {\n\t\tctx = cmd.WithDaemonSubCommands(ctx)\n\t} else {\n\t\tctx = cmd.WithSubCommands(ctx)\n\t}\n\treturn ctx\n}\n\nfunc Main(ctx context.Context, args []string) {\n\tif dir := os.Getenv(\"DEV_TELEPRESENCE_CONFIG_DIR\"); dir != \"\" {\n\t\tctx = filelocation.WithAppUserConfigDir(ctx, dir)\n\t}\n\tif dir := os.Getenv(\"DEV_TELEPRESENCE_LOG_DIR\"); dir != \"\" {\n\t\tctx = filelocation.WithAppUserLogDir(ctx, dir)\n\t}\n\n\tif client.IsDaemon() {\n\t\t// Avoid the initialization of all subcommands except for [userd|rootd|kubeauthd] and\n\t\t// avoids checks for legacy commands.\n\t\tif command, _, err := output.Execute(cmd.TelepresenceDaemon(ctx, args)); err != nil {\n\t\t\tif command != nil {\n\t\t\t\tioutil.Printf(command.ErrOrStderr(), \"%s: error: %v\\n\", command.CommandPath(), err)\n\t\t\t}\n\t\t\tos.Exit(1)\n\t\t}\n\t} else {\n\t\tif command, fmtOutput, err := output.Execute(cmd.Telepresence(ctx, args)); err != nil {\n\t\t\texitCode := 1\n\t\t\tvar exitErr *exec.ExitError\n\t\t\tif errors.As(err, &exitErr) {\n\t\t\t\texitCode = exitErr.ExitCode()\n\t\t\t}\n\t\t\tif fmtOutput || errcat.GetCategory(err) == errcat.Silent {\n\t\t\t\tos.Exit(exitCode)\n\t\t\t}\n\t\t\tif command != nil {\n\t\t\t\tioutil.Printf(command.ErrOrStderr(), \"%s: error: %v\\n\", command.CommandPath(), err)\n\t\t\t\tif errcat.GetCategory(err) > errcat.NoDaemonLogs {\n\t\t\t\t\tif summarizeLogs(ctx, command) {\n\t\t\t\t\t\t// If the user gets here, it might be an actual bug that they found, so\n\t\t\t\t\t\t// point them to the `gather-logs` command in case they want to open an\n\t\t\t\t\t\t// issue.\n\t\t\t\t\t\tioutil.Println(command.ErrOrStderr(), \"If you think you have encountered a bug\"+\n\t\t\t\t\t\t\t\", please run `telepresence gather-logs` and attach the \"+\n\t\t\t\t\t\t\t\"telepresence_logs.zip to your github issue or create a new one: \"+\n\t\t\t\t\t\t\t\"https://github.com/telepresenceio/telepresence/issues/new?template=Bug_report.md .\")\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tioutil.Printf(os.Stderr, \"%v\\n\", err)\n\t\t\t}\n\t\t\tos.Exit(exitCode)\n\t\t}\n\t}\n}\n\n// summarizeLogs outputs the logs from the root and user daemons. It returns true\n// if output were produced, false otherwise (might happen if no logs exist yet).\nfunc summarizeLogs(ctx context.Context, cmd *cobra.Command) bool {\n\tw := cmd.ErrOrStderr()\n\tfirst := true\n\tfor _, processName := range []string{client.RootDaemonName, client.UserDaemonName} {\n\t\tif summary, err := logging.SummarizeLog(ctx, processName); err != nil {\n\t\t\tioutil.Printf(w, \"failed to scan %s logs: %v\\n\", processName, err)\n\t\t} else if summary != \"\" {\n\t\t\tif first {\n\t\t\t\tioutil.Println(w)\n\t\t\t\tfirst = false\n\t\t\t}\n\t\t\tioutil.Println(w, summary)\n\t\t}\n\t}\n\treturn !first\n}\n"
  },
  {
    "path": "pkg/client/cli/mount/flags.go",
    "content": "package mount\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype Flags struct {\n\tLocalMountPort uint16 // --local-mount-port\n\tMount          string // --mount // \"true\", \"false\", or desired mount point\n\tEnabled        bool\n\tReadOnly       bool\n}\n\nfunc (f *Flags) AddFlags(flagSet *pflag.FlagSet, forceReadOnly bool) {\n\tmountText := `The absolute path for the root directory where volumes will be mounted, $TELEPRESENCE_ROOT. Use \"true\" to ` +\n\t\t`have Telepresence pick a random mount point (default). Use \"false\" to disable filesystem mounting entirely.`\n\tif !forceReadOnly {\n\t\tmountText += ` Append \":ro\" to mount everything read-only.`\n\t}\n\tflagSet.StringVar(&f.Mount, \"mount\", \"true\", mountText)\n\n\tflagSet.Uint16Var(&f.LocalMountPort, \"local-mount-port\", 0,\n\t\t`Do not mount remote directories. Instead, expose this port on localhost to an external mounter`)\n\tf.ReadOnly = forceReadOnly\n}\n\nfunc (f *Flags) Validate(cmd *cobra.Command) error {\n\tif f.LocalMountPort > 0 && client.GetConfig(cmd.Context()).Intercept().UseFtp {\n\t\treturn errcat.User.New(\"only SFTP can be used with --local-mount-port. Client is configured to perform remote mounts using FTP\")\n\t}\n\tif !cmd.Flag(\"mount\").Changed {\n\t\t// Default is that mount is enabled and the path is unspecified\n\t\tf.Mount = \"\" // Get rid of the default string \"true\"\n\t\tf.Enabled = true\n\t} else if len(f.Mount) > 0 {\n\t\tif strings.HasSuffix(f.Mount, \":ro\") {\n\t\t\tf.ReadOnly = true\n\t\t\tf.Mount = f.Mount[:len(f.Mount)-3]\n\t\t}\n\t\tdoMount, err := strconv.ParseBool(f.Mount)\n\t\tif err != nil {\n\t\t\t// Not a boolean flag. Must be a path then\n\t\t\tf.Enabled = true\n\t\t} else {\n\t\t\t// Boolean flag, path unspecified\n\t\t\tf.Enabled = doMount\n\t\t\tf.Mount = \"\"\n\t\t\tf.LocalMountPort = 0\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (f *Flags) ValidateConnected(ctx context.Context) (err error) {\n\tif !f.Enabled {\n\t\treturn nil\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tf.Enabled = false\n\t\t\tf.Mount = \"\"\n\t\t\tf.LocalMountPort = 0\n\t\t}\n\t}()\n\n\tud := daemon.MustGetUserClient(ctx)\n\tif ud.Containerized() {\n\t\t// Mounts will be facilitated by the Telemount plug-in connecting to our LocalMountPort\n\t\tif f.LocalMountPort == 0 {\n\t\t\tvar lma []netip.AddrPort\n\t\t\tlma, err = ioutil.FreePortsTCP(1)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tf.LocalMountPort = lma[0].Port()\n\t\t}\n\t\treturn nil\n\t}\n\n\tif err = checkCapability(ctx); err != nil {\n\t\terr = fmt.Errorf(\"remote volume mounts are disabled: %w\", err)\n\t\t// Log a warning and disable, but continue\n\t\tf.Enabled = false\n\t\tf.Mount = \"\"\n\t\tf.LocalMountPort = 0\n\t\tclog.Warn(ctx, err)\n\t\treturn err\n\t}\n\n\tvar cwd string\n\tcwd, err = os.Getwd()\n\tif err != nil {\n\t\treturn err\n\t}\n\tf.Mount, err = prepare(ctx, cwd, f.Mount)\n\treturn err\n}\n\nfunc checkCapability(ctx context.Context) error {\n\t_, err := daemon.MustGetUserClient(ctx).RemoteMountAvailability(ctx, &empty.Empty{})\n\tif err != nil {\n\t\terr = grpc.FromGRPC(err)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/cli/mount/info.go",
    "content": "package mount\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype Info struct {\n\tLocalDir  string              `json:\"local_dir,omitempty\"     yaml:\"local_dir,omitempty\"`\n\tRemoteDir string              `json:\"remote_dir,omitempty\"    yaml:\"remote_dir,omitempty\"`\n\tError     string              `json:\"error,omitempty\"         yaml:\"error,omitempty\"`\n\tPodIP     string              `json:\"pod_ip,omitempty\"        yaml:\"pod_ip,omitempty\"`\n\tPort      uint16              `json:\"port,omitempty\"          yaml:\"port,omitempty\"`\n\tMounts    types.MountPolicies `json:\"mounts,omitempty\"        yaml:\"mounts,omitempty\"`\n\tReadOnly  bool                `json:\"read_only,omitempty\"     yaml:\"read_only,omitempty\"`\n}\n\nfunc NewInfo(ctx context.Context, env map[string]string, ftpPort, sftpPort uint16, localDir, remoteDir, podIP string, mounts types.MountPolicies, ro bool) *Info {\n\tvar port uint16\n\tif client.GetConfig(ctx).Intercept().UseFtp {\n\t\tport = ftpPort\n\t} else {\n\t\tport = sftpPort\n\t}\n\tif mounts == nil {\n\t\t// Older traffic-managers will not provide the mount-path -> mount-policy map, so we must\n\t\t// create it from the TELEPRESENCE_MOUNTS environment.\n\t\tif tpMounts := env[\"TELEPRESENCE_MOUNTS\"]; tpMounts != \"\" {\n\t\t\t// This is a Unix path, so we cannot use filepath.SplitList\n\t\t\tpaths := strings.Split(tpMounts, \":\")\n\t\t\tmounts = make(types.MountPolicies, len(paths))\n\t\t\tmp := types.MountPolicyRemote\n\t\t\tif ro {\n\t\t\t\tmp = types.MountPolicyRemoteReadOnly\n\t\t\t}\n\t\t\tfor _, path := range paths {\n\t\t\t\tif path == \"/tmp\" && mp == types.MountPolicyRemoteReadOnly {\n\t\t\t\t\t// A read-only mount of /tmp is probably pointless\n\t\t\t\t\tmounts[path] = types.MountPolicyLocal\n\t\t\t\t} else {\n\t\t\t\t\tmounts[path] = mp\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn &Info{\n\t\tLocalDir:  localDir,\n\t\tRemoteDir: remoteDir,\n\t\tPodIP:     podIP,\n\t\tPort:      port,\n\t\tMounts:    mounts,\n\t\tReadOnly:  ro,\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/mount/prepare_unix.go",
    "content": "//go:build !windows\n\npackage mount\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\nfunc prepare(ctx context.Context, cwd string, mountPoint string) (string, error) {\n\tif mountPoint == \"\" {\n\t\treturn os.MkdirTemp(client.GetConfig(ctx).Intercept().MountsRoot, \"telfs-\")\n\t}\n\n\t// filepath.Abs uses os.Getwd but we need the working dir of the cli\n\tif !filepath.IsAbs(mountPoint) {\n\t\tmountPoint = filepath.Join(cwd, mountPoint)\n\t\tmountPoint = filepath.Clean(mountPoint)\n\t}\n\n\treturn mountPoint, os.MkdirAll(mountPoint, 0o700)\n}\n"
  },
  {
    "path": "pkg/client/cli/mount/prepare_windows.go",
    "content": "package mount\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n)\n\nfunc prepare(_ context.Context, _ string, mountPoint string) (string, error) {\n\tvar err error\n\tif mountPoint == \"\" {\n\t\t// Find a free drive letter. Background at T, loop around and skip C and D,\n\t\t// A and B aren't often used nowadays. No floppy-disks.\n\t\tfor _, c := range \"TUVXYZABEFGHIJKLMNOPQR\" {\n\t\t\t_, err = os.Stat(fmt.Sprintf(`%c:\\`, c))\n\t\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\t\treturn fmt.Sprintf(`%c:`, c), nil\n\t\t\t}\n\t\t}\n\t\treturn \"\", errcat.User.New(\"found no available drive to use as mount point\")\n\t}\n\n\t// Mount point must be a drive letter\n\tok := len(mountPoint) == 2 && mountPoint[1] == ':'\n\tif ok {\n\t\tdl := mountPoint[0]\n\t\tok = dl >= 'A' && dl <= 'Z' || dl >= 'a' && dl <= 'z'\n\t}\n\tif !ok {\n\t\terr = errcat.User.New(\"mount point must be a drive letter followed by a colon\")\n\t}\n\treturn mountPoint, err\n}\n"
  },
  {
    "path": "pkg/client/cli/output/output.go",
    "content": "// Package output provides structured output for *cobra.Command.\n// Formatted output is enabled by setting the --output=[json|yaml] flag.\npackage output\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\t\"github.com/spf13/cobra\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n)\n\n// Out returns an io.Writer that writes to the OutOrStdout of the current *cobra.Command, or\n// if no command is active, to the os.Stdout. If formatted output is requested, the output\n// will be delayed until Execute is called.\nfunc Out(ctx context.Context) io.Writer {\n\tif cmd, ok := ctx.Value(key{}).(*cobra.Command); ok {\n\t\treturn cmd.OutOrStdout()\n\t}\n\treturn dos.Stdout(ctx)\n}\n\n// Err returns an io.Writer that writes to the ErrOrStderr of the current *cobra.Command, or\n// if no command is active, to the os.Stderr. If formatted output is requested, the output\n// will be delayed until Execute is called.\nfunc Err(ctx context.Context) io.Writer {\n\tif cmd, ok := ctx.Value(key{}).(*cobra.Command); ok {\n\t\treturn cmd.ErrOrStderr()\n\t}\n\treturn dos.Stderr(ctx)\n}\n\n// Info is similar to Out, but if formatted output is requested, the output will be discarded.\n//\n// Info is primarily intended for messages that are not directly related to the command that\n// executes, such as messages about starting up daemons or being connected to a context.\nfunc Info(ctx context.Context) io.Writer {\n\tif cmd, ok := ctx.Value(key{}).(*cobra.Command); ok {\n\t\tif _, ok := cmd.OutOrStdout().(*output); ok {\n\t\t\treturn io.Discard\n\t\t}\n\t\treturn cmd.OutOrStdout()\n\t}\n\treturn dos.Stdout(ctx)\n}\n\n// Object sets the object to be marshalled and printed on stdout when formatted output\n// is requested using the `--output=<fmt>` flag. Otherwise, this function does nothing.\n//\n// If override is set to true, then the formatted output will consist solely of the given\n// object. There will be no \"cmd\", \"stdout\", or \"stderr\" tags.\n//\n// The function will panic if data already has been written to the stdout of the command\n// or if an Object already has been called.\nfunc Object(ctx context.Context, obj any, override bool) {\n\tif cmd, ok := ctx.Value(key{}).(*cobra.Command); ok {\n\t\tif o, ok := cmd.OutOrStdout().(*output); ok {\n\t\t\tif o.Len() > 0 {\n\t\t\t\tpanic(\"output.Object cannot be used together with output.Out\")\n\t\t\t}\n\t\t\tif o.obj != nil {\n\t\t\t\tpanic(\"output.Object can only be used once\")\n\t\t\t}\n\n\t\t\tif o.format == formatJSONStream {\n\t\t\t\tdata, err := json.Marshal(obj)\n\t\t\t\tif err == nil {\n\t\t\t\t\t_, err = o.originalStdout.Write(data)\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\tpanic(err)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\to.obj = obj\n\t\t\t}\n\n\t\t\to.override = override\n\t\t}\n\t}\n}\n\n// DefaultYAML is a PersistentPRERunE function that will change the default output\n// format to \"yaml\" for the command that invokes it.\nfunc DefaultYAML(cmd *cobra.Command, _ []string) error {\n\tfmt, err := validateFlag(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\trootCmd := cmd\n\tfor {\n\t\tp := rootCmd.Parent()\n\t\tif p == nil {\n\t\t\tbreak\n\t\t}\n\t\trootCmd = p\n\t}\n\tif fmt == formatDefault {\n\t\tif err = rootCmd.PersistentFlags().Set(global.FlagOutput, \"yaml\"); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn rootCmd.PersistentPreRunE(cmd, cmd.Flags().Args())\n}\n\n// Execute will call ExecuteC on the given command, optionally print all formatted\n// output, and return a boolean indicating if formatted output was printed. The\n// result of the execution is provided in the second return value.\nfunc Execute(cmd *cobra.Command) (*cobra.Command, bool, error) {\n\tcmd, err := cmd.ExecuteC()\n\to, ok := cmd.OutOrStdout().(*output)\n\tif !ok {\n\t\treturn cmd, false, err\n\t}\n\n\tvar obj any\n\tif err == nil && o.override {\n\t\tobj = o.obj\n\t} else {\n\t\tresponse := &object{\n\t\t\tCmd: cmd.Name(),\n\t\t}\n\t\tif buf := o.Buffer; buf.Len() > 0 {\n\t\t\tresponse.Stdout = buf.String()\n\t\t} else if o.obj != nil {\n\t\t\tresponse.Stdout = o.obj\n\t\t}\n\t\tif buf, ok := cmd.ErrOrStderr().(*bytes.Buffer); ok && buf.Len() > 0 {\n\t\t\tresponse.Stderr = buf.String()\n\t\t}\n\t\tif err != nil {\n\t\t\tresponse.Err = err.Error()\n\t\t}\n\t\t// don't print out the \"zero\" object\n\t\tif response.hasCmdOnly() {\n\t\t\treturn cmd, true, err\n\t\t}\n\t\tobj = response\n\t}\n\tswitch o.format {\n\tcase formatJSON:\n\t\tdata, encErr := json.Marshal(obj)\n\t\tif encErr == nil {\n\t\t\t_, encErr = o.originalStdout.Write(data)\n\t\t}\n\t\tif encErr != nil {\n\t\t\tpanic(encErr)\n\t\t}\n\tcase formatYAML:\n\t\tym, encErr := json.Marshal(obj)\n\t\tif encErr == nil {\n\t\t\tym, encErr = yaml.JSONToYAML(ym)\n\t\t\tif encErr == nil {\n\t\t\t\t_, encErr = o.originalStdout.Write(ym)\n\t\t\t}\n\t\t}\n\t\tif encErr != nil {\n\t\t\tpanic(encErr)\n\t\t}\n\tcase formatJSONStream:\n\tdefault:\n\t\tfmt.Fprintf(o.originalStdout, \"%+v\", obj)\n\t}\n\treturn cmd, true, err\n}\n\n// SetFormat assigns a cobra.Command.PersistentPreRunE function that all sub commands will inherit. This\n// function checks if the global `--output` flag was used, and if so, ensures that formatted output is\n// initialized.\nfunc SetFormat(cmd *cobra.Command, _ []string) error {\n\terr := global.InitConfig(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt, err := validateFlag(cmd)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif fmt != formatDefault {\n\t\to := output{\n\t\t\tformat:         fmt,\n\t\t\toriginalStdout: cmd.OutOrStdout(),\n\t\t}\n\t\tcmd.SetOut(&o)\n\t\tcmd.SetErr(&bytes.Buffer{})\n\t\tcmd.SilenceErrors = true\n\t\tcmd.SilenceUsage = true\n\t}\n\tcmd.SetContext(context.WithValue(cmd.Context(), key{}, cmd))\n\treturn nil\n}\n\n// WantsFormatted returns true if the value of the global `--output` flag is set to a valid\n// format different from \"default\".\nfunc WantsFormatted(cmd *cobra.Command) bool {\n\tf, _ := validateFlag(cmd)\n\treturn f != formatDefault\n}\n\n// WantsStream returns true if the value of the global `--output` flag is set to \"json-stream\".\nfunc WantsStream(cmd *cobra.Command) bool {\n\tf, _ := validateFlag(cmd)\n\treturn f == formatJSONStream\n}\n\nfunc validateFlag(cmd *cobra.Command) (format, error) {\n\tif of := cmd.Flags().Lookup(global.FlagOutput); of != nil && of.DefValue == \"default\" {\n\t\tfmt := strings.ToLower(of.Value.String())\n\t\tswitch fmt {\n\t\tcase \"yaml\":\n\t\t\treturn formatYAML, nil\n\t\tcase \"json\":\n\t\t\treturn formatJSON, nil\n\t\tcase \"json-stream\":\n\t\t\treturn formatJSONStream, nil\n\t\tcase \"default\":\n\t\t\treturn formatDefault, nil\n\t\tdefault:\n\t\t\treturn formatDefault, errcat.User.Newf(\"invalid output format %q\", fmt)\n\t\t}\n\t}\n\treturn formatDefault, nil\n}\n\ntype (\n\tformat int\n\tkey    struct{}\n\toutput struct {\n\t\tbytes.Buffer\n\t\tformat         format\n\t\tobj            any\n\t\toverride       bool\n\t\toriginalStdout io.Writer\n\t}\n\tobject struct {\n\t\tCmd    string `json:\"cmd\"`\n\t\tStdout any    `json:\"stdout,omitempty\"`\n\t\tStderr any    `json:\"stderr,omitempty\"`\n\t\tErr    string `json:\"err,omitempty\"`\n\t}\n)\n\nconst (\n\tformatDefault = format(iota)\n\tformatJSON\n\tformatYAML\n\tformatJSONStream\n)\n\nfunc (o *output) Write(data []byte) (int, error) {\n\tif o.obj != nil {\n\t\tpanic(\"Stdout cannot be used together with output.Object\")\n\t}\n\treturn o.Buffer.Write(data)\n}\n\nfunc (o *object) hasCmdOnly() bool {\n\treturn o.Stdout == nil && o.Stderr == nil && o.Err == \"\"\n}\n"
  },
  {
    "path": "pkg/client/cli/output/output_test.go",
    "content": "package output\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/stretchr/testify/require\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\nfunc TestWithOutput(t *testing.T) {\n\texpectedREStdout := \"re\\n\"\n\texpectedREStderr := \"re_stderr\\n\"\n\texpectedName := \"testing\"\n\n\tre := func(cmd *cobra.Command, args []string) error {\n\t\tstdout := cmd.OutOrStdout()\n\t\tstderr := cmd.ErrOrStderr()\n\t\tioutil.Print(stdout, expectedREStdout)\n\t\tioutil.Print(stderr, expectedREStderr)\n\t\treturn nil\n\t}\n\n\tnewCmdWithBufs := func() (*cobra.Command, *strings.Builder, *strings.Builder) {\n\t\tstdoutBuf := strings.Builder{}\n\t\tstderrBuf := strings.Builder{}\n\t\tcmd := cobra.Command{}\n\n\t\tcmd.Use = expectedName\n\t\tcmd.SetOut(&stdoutBuf)\n\t\tcmd.SetErr(&stderrBuf)\n\t\tcmd.SetContext(context.Background())\n\t\tcmd.PersistentPreRunE = SetFormat\n\t\tcmd.RunE = re\n\n\t\tcmd.PersistentFlags().String(global.FlagOutput, \"default\", \"\")\n\n\t\treturn &cmd, &stdoutBuf, &stderrBuf\n\t}\n\n\tt.Run(\"non-json output\", func(t *testing.T) {\n\t\tcmd, outBuf, errBuf := newCmdWithBufs()\n\t\t_, _, err := Execute(cmd)\n\t\trequire.NoError(t, err)\n\n\t\trequire.Equal(t, expectedREStdout, outBuf.String(), \"did not get expected stdout\")\n\t\trequire.Equal(t, expectedREStderr, errBuf.String(), \"did not get expected stderr\")\n\t})\n\n\tt.Run(\"json output no error\", func(t *testing.T) {\n\t\tcmd, outBuf, errBuf := newCmdWithBufs()\n\t\tcmd.SetArgs([]string{\"--output=json\"})\n\t\t_, _, err := Execute(cmd)\n\t\trequire.NoError(t, err)\n\n\t\tstdout := outBuf.String()\n\t\tm := map[string]string{}\n\t\trequire.NoError(t, json.Unmarshal([]byte(stdout), &m), \"did not get json as stdout, got: %s\", stdout)\n\t\trequire.Equal(t, expectedREStdout, m[\"stdout\"], \"did not get expected stdout, got: %s\", m[\"stdout\"])\n\t\trequire.Equal(t, expectedREStderr, m[\"stderr\"], \"did not get expected stderr, got: %s\", m[\"stderr\"])\n\t\trequire.Equal(t, expectedName, m[\"cmd\"], \"did not get expected cmd name, got: %s\", m[\"cmd\"])\n\n\t\tstderr := errBuf.String()\n\t\trequire.Empty(t, stderr, \"expected empty stderr, got: %s\", stderr)\n\t})\n\n\tt.Run(\"json output with error\", func(t *testing.T) {\n\t\texpectedErr := \"ERROR\"\n\t\tcmd, outBuf, _ := newCmdWithBufs()\n\t\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\t\treturn errors.New(expectedErr)\n\t\t}\n\t\tcmd.SetArgs([]string{\"--output=json\"})\n\t\t_, _, err := Execute(cmd)\n\t\trequire.Error(t, err)\n\n\t\tstdout := outBuf.String()\n\t\tm := map[string]string{}\n\t\trequire.NoError(t, json.Unmarshal([]byte(stdout), &m), \"did not get json as stdout, got: %s\", stdout)\n\t\trequire.Equal(t, expectedErr, m[\"err\"], \"did not get expected err, got: %s\", m[\"err\"])\n\t})\n\n\tt.Run(\"yaml output with error\", func(t *testing.T) {\n\t\texpectedErr := \"ERROR\"\n\t\tcmd, outBuf, _ := newCmdWithBufs()\n\t\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\t\treturn errors.New(expectedErr)\n\t\t}\n\t\tcmd.SetArgs([]string{\"--output=yaml\"})\n\t\t_, _, err := Execute(cmd)\n\t\trequire.Error(t, err)\n\n\t\tstdout := outBuf.String()\n\t\tm := map[string]string{}\n\t\trequire.NoError(t, yaml.Unmarshal([]byte(stdout), &m), \"did not get yaml as stdout, got: %s\", stdout)\n\t\trequire.Equal(t, expectedErr, m[\"err\"], \"did not get expected err, got: %s\", m[\"err\"])\n\t})\n\n\tt.Run(\"json output with native json\", func(t *testing.T) {\n\t\texpectedNativeJSONMap := map[string]float64{\n\t\t\t\"a\": 1,\n\t\t}\n\t\tcmd, outBuf, errBuf := newCmdWithBufs()\n\t\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\t\tObject(cmd.Context(), expectedNativeJSONMap, false)\n\t\t\treturn nil\n\t\t}\n\t\tcmd.SetArgs([]string{\"--output=json\"})\n\t\t_, _, err := Execute(cmd)\n\t\trequire.NoError(t, err)\n\n\t\tstdout := outBuf.String()\n\t\tm := map[string]any{}\n\t\trequire.NoError(t, json.Unmarshal([]byte(stdout), &m), \"did not get json as stdout, got: %s\", stdout)\n\t\tjsonOutputBytes, err := json.Marshal(m[\"stdout\"])\n\t\trequire.NoError(t, err, \"did not get json stdout as expected\")\n\t\texpectedJSONOutputBytes, _ := json.Marshal(expectedNativeJSONMap)\n\n\t\trequire.Equal(t, expectedJSONOutputBytes, jsonOutputBytes, \"did not get expected stdout json\")\n\t\tstderr := errBuf.String()\n\t\trequire.Empty(t, stderr, \"expected empty stderr, got: %s\", stderr)\n\t})\n\n\tt.Run(\"json output with native json and other output\", func(t *testing.T) {\n\t\texpectedNativeJSONMap := map[string]float64{\n\t\t\t\"a\": 1,\n\t\t}\n\t\tcmd, _, _ := newCmdWithBufs()\n\t\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\t\tObject(cmd.Context(), expectedNativeJSONMap, false)\n\t\t\tfmt.Fprintln(cmd.OutOrStdout(), \"hello\")\n\t\t\treturn nil\n\t\t}\n\t\tcmd.SetArgs([]string{\"--output=json\"})\n\t\trequire.Panics(t, func() {\n\t\t\t_, _, _ = Execute(cmd)\n\t\t})\n\t})\n\n\tt.Run(\"json output with other output and native json\", func(t *testing.T) {\n\t\texpectedNativeJSONMap := map[string]float64{\n\t\t\t\"a\": 1,\n\t\t}\n\t\tcmd, _, _ := newCmdWithBufs()\n\t\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\t\tfmt.Fprintln(cmd.OutOrStdout(), \"hello\")\n\t\t\tObject(cmd.Context(), expectedNativeJSONMap, false)\n\t\t\treturn nil\n\t\t}\n\t\tcmd.SetArgs([]string{\"--output=json\"})\n\t\trequire.Panics(t, func() {\n\t\t\t_, _, _ = Execute(cmd)\n\t\t})\n\t})\n\n\tt.Run(\"json output with multiple native json\", func(t *testing.T) {\n\t\texpectedNativeJSONMap := map[string]float64{\n\t\t\t\"a\": 1,\n\t\t}\n\t\tcmd, _, _ := newCmdWithBufs()\n\t\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\t\tObject(cmd.Context(), expectedNativeJSONMap, false)\n\t\t\tObject(cmd.Context(), expectedNativeJSONMap, false)\n\t\t\treturn nil\n\t\t}\n\t\tcmd.SetArgs([]string{\"--output=json\"})\n\t\trequire.Panics(t, func() {\n\t\t\t_, _, _ = Execute(cmd)\n\t\t})\n\t})\n\n\tt.Run(\"json output with overriding native json\", func(t *testing.T) {\n\t\texpectedNativeJSONMap := map[string]float64{\n\t\t\t\"a\": 1,\n\t\t}\n\t\tcmd, outBuf, _ := newCmdWithBufs()\n\t\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\t\tObject(cmd.Context(), expectedNativeJSONMap, true)\n\t\t\treturn nil\n\t\t}\n\t\tcmd.SetArgs([]string{\"--output=json\"})\n\t\t_, _, err := Execute(cmd)\n\t\trequire.NoError(t, err)\n\n\t\tstdout := outBuf.String()\n\t\tm := map[string]any{}\n\t\trequire.NoError(t, json.Unmarshal([]byte(stdout), &m), \"did not get json as stdout, got: %s\", stdout)\n\t\trequire.Equal(t, 1.0, m[\"a\"])\n\t})\n\n\tt.Run(\"json output with overriding native json and error\", func(t *testing.T) {\n\t\texpectedNativeMap := map[string]any{\n\t\t\t\"a\": 1.0,\n\t\t}\n\t\tcmd, outBuf, _ := newCmdWithBufs()\n\t\tcmd.RunE = func(cmd *cobra.Command, args []string) error {\n\t\t\tObject(cmd.Context(), expectedNativeMap, true)\n\t\t\treturn errors.New(\"this went south\")\n\t\t}\n\t\tcmd.SetArgs([]string{\"--output=json\"})\n\t\t_, _, err := Execute(cmd)\n\t\trequire.Error(t, err)\n\n\t\tstdout := outBuf.String()\n\t\tm := map[string]any{}\n\t\trequire.NoError(t, json.Unmarshal([]byte(stdout), &m), \"did not get json as stdout, got: %s\", stdout)\n\t\trequire.Equal(t, m[\"stdout\"], expectedNativeMap, \"did not get expected stdout\")\n\t\trequire.Empty(t, m[\"stderr\"], \"did not get empty stderr\")\n\t\trequire.Equal(t, m[\"err\"], \"this went south\")\n\t})\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/colors.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"github.com/morikuni/aec\"\n)\n\ntype noColor struct{}\n\nfunc (a noColor) With(_ ...aec.ANSI) aec.ANSI {\n\treturn a\n}\n\nfunc (noColor) Apply(s string) string {\n\treturn s\n}\n\nfunc (noColor) String() string {\n\treturn \"\"\n}\n\nvar (\n\tdoneColor    = aec.BlueF                  //nolint:gochecknoglobals // constant names\n\ttimerColor   = aec.BlueF                  //nolint:gochecknoglobals // constant names\n\tcountColor   = aec.YellowF                //nolint:gochecknoglobals // constant names\n\tinfoColor    = aec.LightBlueF             //nolint:gochecknoglobals // constant names\n\twarningColor = aec.YellowF.With(aec.Bold) //nolint:gochecknoglobals // constant names\n\tsuccessColor = aec.GreenF                 //nolint:gochecknoglobals // constant names\n\terrorColor   = aec.RedF.With(aec.Bold)    //nolint:gochecknoglobals // constant names\n)\n"
  },
  {
    "path": "pkg/client/cli/progress/event.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n\t\"github.com/morikuni/aec\"\n)\n\n// EventStatus indicates the status of an action.\ntype EventStatus int\n\nconst (\n\tEventStatusWorking EventStatus = iota\n\tEventStatusDone\n\tEventStatusInfo\n\tEventStatusWarning\n\tEventStatusError\n)\n\nfunc (s EventStatus) color() aec.ANSI {\n\tswitch s {\n\tcase EventStatusDone:\n\t\treturn successColor\n\tcase EventStatusInfo:\n\t\treturn infoColor\n\tcase EventStatusWarning:\n\t\treturn warningColor\n\tcase EventStatusError:\n\t\treturn errorColor\n\tdefault:\n\t\treturn noColor{}\n\t}\n}\n\nfunc (s EventStatus) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, s.String())\n}\n\nfunc (s EventStatus) String() string {\n\tswitch s {\n\tcase EventStatusDone:\n\t\treturn \"Done\"\n\tcase EventStatusWarning:\n\t\treturn \"Warning\"\n\tcase EventStatusError:\n\t\treturn \"Error\"\n\tcase EventStatusInfo:\n\t\treturn \"Info\"\n\tdefault:\n\t\treturn \"Working\"\n\t}\n}\n\n// Event represents a progress event.\ntype Event struct {\n\tID          string      `json:\"id,omitempty\"`\n\tText        string      `json:\"text,omitempty\"`\n\tStatus      EventStatus `json:\"status,omitempty\"`\n\tStatusText  string      `json:\"statusText,omitempty\"`\n\tCurrent     int64       `json:\"current,omitempty\"`\n\tPercent     int         `json:\"percent,omitempty\"`\n\tTotal       int64       `json:\"total,omitempty\"`\n\tStartTime   time.Time   `json:\"startTime,omitzero\"`\n\tEndTime     time.Time   `json:\"endTime,omitzero\"`\n\tplainAlways bool\n\tspinner     *spinner\n\tchildren    []*Event\n}\n\n// ErrorMessageEvent creates a new Error Event with a message.\nfunc ErrorMessageEvent(id string, msg string) *Event {\n\treturn NewEvent(id, EventStatusError, msg)\n}\n\n// WarningMessageEvent creates a new Error Event with a message.\nfunc WarningMessageEvent(id string, msg string) *Event {\n\treturn NewEvent(id, EventStatusWarning, msg)\n}\n\n// InfoMessageEvent creates a new Error Event with a message.\nfunc InfoMessageEvent(id string, msg string) *Event {\n\treturn NewEvent(id, EventStatusInfo, msg)\n}\n\n// StartingEvent creates a new Starting in progress Event.\nfunc StartingEvent(id string) *Event {\n\treturn NewEvent(id, EventStatusWorking, \"Starting\")\n}\n\n// StartedEvent creates a new Started in progress Event.\nfunc StartedEvent(id string) *Event {\n\treturn NewEvent(id, EventStatusDone, \"Started\")\n}\n\n// StoppedEvent creates a new Stopping in progress Event.\nfunc StoppedEvent(id string) *Event {\n\treturn NewEvent(id, EventStatusDone, \"Stopped\")\n}\n\n// BuildingEvent creates a new Building in progress Event.\nfunc BuildingEvent(id string) *Event {\n\treturn NewEvent(id, EventStatusWorking, \"Building\")\n}\n\n// BuiltEvent creates a new built (done) *Event.\nfunc BuiltEvent(id string) *Event {\n\treturn NewEvent(id, EventStatusDone, \"Built\")\n}\n\n// WorkingEvent creates a new <verb> in progress Event.\nfunc WorkingEvent(id, verb string) *Event {\n\treturn NewEvent(id, EventStatusWorking, verb)\n}\n\n// DoneEvent creates a new <verb> done Event.\nfunc DoneEvent(id, verb string) *Event {\n\treturn NewEvent(id, EventStatusDone, verb)\n}\n\nfunc NewEvent(id string, status EventStatus, statusText string) *Event {\n\te := &Event{\n\t\tID:         id,\n\t\tStatus:     status,\n\t\tStatusText: statusText,\n\t}\n\tswitch status {\n\tcase EventStatusWorking:\n\t\te.spinner = newSpinner()\n\t\te.StartTime = time.Now()\n\tcase EventStatusDone, EventStatusError:\n\t\te.EndTime = time.Now()\n\tdefault:\n\t}\n\treturn e\n}\n\nfunc (e *Event) WithText(msg string) *Event {\n\te.Text = msg\n\treturn e\n}\n\nfunc (e *Event) AddChild(status EventStatus, text, statusText string) *Event {\n\tchild := NewEvent(fmt.Sprintf(\"%s-%d\", e.ID, len(e.children)+1), status, statusText)\n\tchild.Text = text\n\te.children = append(e.children, child)\n\treturn child\n}\n\n// PlainAlways configures the event to always be printed when using the plain progress writer.\nfunc (e *Event) PlainAlways() *Event {\n\te.plainAlways = true\n\treturn e\n}\n\nfunc (e *Event) Pump(ctx context.Context, status EventStatus) io.Writer {\n\tin, out := io.Pipe()\n\twr := ContextWriter(ctx)\n\tid := e.ID\n\tif id == \"\" {\n\t\tid = EventId(ctx)\n\t}\n\tgo func() {\n\t\tfor {\n\t\t\tvar buf [1024]byte\n\t\t\tn, err := in.Read(buf[:])\n\t\t\tif n > 0 {\n\t\t\t\twr.Write(NewEvent(id, status, strings.TrimSpace(string(buf[:n]))))\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\treturn out\n}\n\nfunc (e *Event) stop() {\n\te.EndTime = time.Now()\n\tif e.spinner != nil {\n\t\te.spinner.Stop()\n\t}\n}\n\nfunc (e *Event) child(id string) *Event {\n\tfor _, child := range e.children {\n\t\tif child.ID == id {\n\t\t\treturn child\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (e *Event) merge(o *Event) {\n\tif e == o {\n\t\treturn\n\t}\n\te.Text = o.Text\n\te.EndTime = o.EndTime\n\n\tswitch e.Status {\n\tcase EventStatusInfo, EventStatusWarning, EventStatusError:\n\t\tif o.Status == EventStatusWorking || o.Status == EventStatusDone {\n\t\t\tch := e.AddChild(e.Status, e.Text, e.StatusText)\n\t\t\tch.plainAlways = e.plainAlways\n\t\t}\n\tdefault:\n\t}\n\n\tswitch o.Status {\n\tcase EventStatusError:\n\t\te.Status = EventStatusError\n\t\te.stop()\n\t\tfallthrough\n\tcase EventStatusInfo, EventStatusWarning:\n\t\te.AddChild(o.Status, o.Text, o.StatusText)\n\tcase EventStatusDone:\n\t\te.stop()\n\t\tfallthrough\n\tcase EventStatusWorking:\n\t\te.spinner = o.spinner\n\t\te.Text = o.Text\n\t\te.Status = o.Status\n\t\te.StatusText = o.StatusText\n\t\t// progress can only go up\n\t\tif o.Total > e.Total {\n\t\t\te.Total = o.Total\n\t\t}\n\t\tif o.Current > e.Current {\n\t\t\te.Current = o.Current\n\t\t}\n\t\tif o.Percent > e.Percent {\n\t\t\te.Percent = o.Percent\n\t\t}\n\t}\n\n\t// Drop Working and Done events from the current event. Merge other events\n\t// with the same ID.\n\tvar children []*Event\n\tfor _, child := range e.children {\n\t\tif oc := o.child(child.ID); oc != nil {\n\t\t\tchild.merge(oc)\n\t\t\tchildren = append(children, child)\n\t\t} else {\n\t\t\tswitch child.Status {\n\t\t\tcase EventStatusWorking, EventStatusDone:\n\t\t\tdefault:\n\t\t\t\tchildren = append(children, child)\n\t\t\t}\n\t\t}\n\t}\n\t// Add new events.\n\tfor _, child := range o.children {\n\t\tif ec := e.child(child.ID); ec == nil {\n\t\t\tchildren = append(children, child)\n\t\t}\n\t}\n\n\tif e.Status == EventStatusDone {\n\t\tfor _, child := range children {\n\t\t\tif child.Status == EventStatusWorking {\n\t\t\t\tchild.Status = EventStatusDone\n\t\t\t}\n\t\t}\n\t}\n\te.children = children\n}\n\nconst (\n\tspinnerDone    = \"✔\"\n\tspinnerWarning = \"!\"\n\tspinnerError   = \"✘\"\n)\n\nfunc (e *Event) Spinner() string {\n\tswitch e.Status {\n\tcase EventStatusDone:\n\t\treturn successColor.Apply(spinnerDone)\n\tcase EventStatusWarning:\n\t\treturn warningColor.Apply(spinnerWarning)\n\tcase EventStatusError:\n\t\treturn errorColor.Apply(spinnerError)\n\tdefault:\n\t\tif e.spinner == nil {\n\t\t\treturn \" \"\n\t\t}\n\t\treturn countColor.Apply(e.spinner.String())\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/json.go",
    "content": "/*\n   Copyright 2024 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/go-json-experiment/json\"\n)\n\ntype jsonWriter struct {\n\tout io.Writer\n}\n\nfunc (p *jsonWriter) Start(context.Context, string) {\n}\n\nfunc (p *jsonWriter) IsNoOp() bool {\n\treturn false\n}\n\nfunc (p *jsonWriter) write(e *Event) {\n\tmarshal, err := json.Marshal(e)\n\tif err == nil {\n\t\t_, _ = fmt.Fprintln(p.out, string(marshal))\n\t}\n}\n\nfunc (p *jsonWriter) Write(events ...*Event) {\n\tfor _, e := range events {\n\t\tp.write(e)\n\t}\n}\n\ntype tailMsg struct {\n\tMessage string `json:\"message\"`\n}\n\nfunc (p *jsonWriter) TailMsgf(msg string, args ...any) {\n\tmarshal, err := json.Marshal(&tailMsg{Message: fmt.Sprintf(msg, args...)})\n\tif err == nil {\n\t\t_, _ = fmt.Fprintln(p.out, string(marshal))\n\t}\n}\n\nfunc (p *jsonWriter) Stop() {\n}\n\nfunc (p *jsonWriter) TriggerRefresh() {\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/noop.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"context\"\n)\n\ntype noopWriter struct{}\n\nfunc (p noopWriter) Start(context.Context, string) {\n}\n\nfunc (p noopWriter) IsNoOp() bool {\n\treturn true\n}\n\nfunc (p noopWriter) Write(...*Event) {\n}\n\nfunc (p noopWriter) TailMsgf(_ string, _ ...any) {\n}\n\nfunc (p noopWriter) Stop() {\n}\n\nfunc (p noopWriter) TriggerRefresh() {\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/plain.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype plainWriter struct {\n\tout io.Writer\n\terr io.Writer\n}\n\nfunc (p plainWriter) IsNoOp() bool {\n\treturn false\n}\n\nfunc (p plainWriter) Start(context.Context, string) {\n}\n\nfunc (p plainWriter) Stop() {\n}\n\nfunc (p plainWriter) Write(events ...*Event) {\n\tfor _, e := range events {\n\t\tw := p.out\n\t\tif e.Status == EventStatusError || e.Status == EventStatusWarning {\n\t\t\tw = p.err\n\t\t}\n\t\tif e.plainAlways || e.Status == EventStatusError || e.Status == EventStatusWarning || e.Status == EventStatusInfo || e.Text != \"\" {\n\t\t\tif e.Text == \"\" {\n\t\t\t\tioutil.Println(w, e.StatusText)\n\t\t\t} else {\n\t\t\t\tioutil.Println(w, e.Text, e.StatusText)\n\t\t\t}\n\t\t}\n\t\tp.Write(e.children...)\n\t}\n}\n\nfunc (p plainWriter) TailMsgf(msg string, args ...any) {\n\t_, _ = fmt.Fprintln(p.out, fmt.Sprintf(msg, args...))\n}\n\nfunc (p plainWriter) TriggerRefresh() {\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/quiet.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport \"context\"\n\ntype quiet struct{}\n\nfunc (q quiet) Start(context.Context, string) {\n}\n\nfunc (q quiet) IsNoOp() bool {\n\treturn false\n}\n\nfunc (q quiet) Stop() {\n}\n\nfunc (q quiet) Write(...*Event) {\n}\n\nfunc (q quiet) TailMsgf(_ string, _ ...any) {\n}\n\nfunc (q quiet) TriggerRefresh() {\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/spinner.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"runtime\"\n\t\"time\"\n)\n\ntype spinner struct {\n\ttime  time.Time\n\tindex int\n\tchars []string\n\tstop  bool\n\tdone  string\n}\n\nfunc newSpinner() *spinner {\n\tchars := []string{\n\t\t\"⠋\", \"⠙\", \"⠹\", \"⠸\", \"⠼\", \"⠴\", \"⠦\", \"⠧\", \"⠇\", \"⠏\",\n\t}\n\tdone := \"⠿\"\n\n\tif runtime.GOOS == \"windows\" {\n\t\tchars = []string{\"-\"}\n\t\tdone = \"-\"\n\t}\n\n\treturn &spinner{\n\t\tindex: 0,\n\t\ttime:  time.Now(),\n\t\tchars: chars,\n\t\tdone:  done,\n\t}\n}\n\nfunc (s *spinner) String() string {\n\tif s.stop {\n\t\treturn s.done\n\t}\n\n\td := time.Since(s.time)\n\tif d.Milliseconds() > 100 {\n\t\ts.index = (s.index + 1) % len(s.chars)\n\t}\n\n\treturn s.chars[s.index]\n}\n\nfunc (s *spinner) Stop() {\n\ts.stop = true\n}\n\nfunc (s *spinner) Restart() {\n\ts.stop = false\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/tty.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/docker/go-units\"\n\t\"github.com/mitchellh/go-wordwrap\"\n\t\"github.com/moby/term\"\n\t\"github.com/morikuni/aec\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype ttyWriter struct {\n\tout             io.Writer\n\tticker          *time.Ticker\n\tevents          map[string]*Event\n\teventIDs        []string\n\trepeated        bool\n\tnumLines        int\n\tdoneOnce        sync.Once\n\tdone            chan struct{}\n\tmtx             sync.Mutex\n\tskipChildEvents bool\n\tprogressTitle   string\n}\n\nfunc newTTYWriter(out io.Writer) Writer {\n\tw := &ttyWriter{\n\t\tout:    out,\n\t\tevents: make(map[string]*Event),\n\t\tdone:   make(chan struct{}),\n\t}\n\tw.ticker = time.NewTicker(math.MaxInt64)\n\treturn w\n}\n\nfunc (w *ttyWriter) Start(ctx context.Context, progressTitle string) {\n\tw.mtx.Lock()\n\tdefer w.mtx.Unlock()\n\tw.events = make(map[string]*Event)\n\tw.eventIDs = nil\n\tw.repeated = false\n\tw.numLines = 0\n\tw.done = make(chan struct{})\n\tw.skipChildEvents = false\n\tw.progressTitle = progressTitle\n\tgo func() {\n\t\tdefer w.ticker.Stop()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-w.ticker.C:\n\t\t\t\tw.print()\n\t\t\tcase <-ctx.Done():\n\t\t\t\tw.print()\n\t\t\t\treturn\n\t\t\tcase <-w.done:\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (w *ttyWriter) IsNoOp() bool {\n\treturn false\n}\n\nfunc (w *ttyWriter) Stop() {\n\tw.doneOnce.Do(func() {\n\t\tclose(w.done)\n\t})\n\tw.print()\n}\n\nfunc (w *ttyWriter) event(e *Event) {\n\tlast, ok := w.events[e.ID]\n\tif ok {\n\t\tlast.merge(e)\n\t} else {\n\t\tw.eventIDs = append(w.eventIDs, e.ID)\n\t\tw.events[e.ID] = e\n\t}\n}\n\nfunc (w *ttyWriter) Write(events ...*Event) {\n\tw.mtx.Lock()\n\tfor _, e := range events {\n\t\tw.event(e)\n\t}\n\tw.mtx.Unlock()\n\tw.TriggerRefresh()\n}\n\nfunc (w *ttyWriter) TriggerRefresh() {\n\tw.ticker.Reset(333 * time.Millisecond)\n}\n\nfunc (w *ttyWriter) print() {\n\tw.mtx.Lock()\n\tdefer w.mtx.Unlock()\n\tif len(w.eventIDs) == 0 {\n\t\treturn\n\t}\n\tws, err := term.GetWinsize(1)\n\tif err != nil {\n\t\tws = &term.Winsize{\n\t\t\tHeight: 25,\n\t\t\tWidth:  80,\n\t\t}\n\t}\n\tb := aec.EmptyBuilder\n\tif w.repeated {\n\t\tb = b.Up(uint(w.numLines))\n\t\tioutil.Print(w.out, b.Column(0).ANSI)\n\t} else {\n\t\tw.repeated = true\n\t}\n\n\t// Hide the cursor while we are printing\n\tioutil.Print(w.out, aec.Hide)\n\tdefer func() {\n\t\tioutil.Print(w.out, aec.Show)\n\t}()\n\n\tnumLines := 0\n\twithID := len(w.eventIDs) > 1\n\tif withID {\n\t\tfirstLine := fmt.Sprintf(\"[+] %s %d/%d\", w.progressTitle, numDone(w.events), len(w.events))\n\t\tif numDone(w.events) == len(w.events) {\n\t\t\tfirstLine = doneColor.Apply(firstLine)\n\t\t}\n\t\tfirstLine += aec.EraseLine(aec.EraseModes.Tail).String()\n\t\tioutil.Println(w.out, firstLine)\n\t\tnumLines++\n\t}\n\n\tvar statusPadding int\n\tfor _, v := range w.eventIDs {\n\t\tevent := w.events[v]\n\t\tl := len(event.Text)\n\t\tif withID {\n\t\t\tif l > 0 {\n\t\t\t\tl++ // one space between text and id\n\t\t\t}\n\t\t\tl += len(event.ID)\n\t\t}\n\t\tif l > 0 {\n\t\t\tl++ // one space after text\n\t\t\tif statusPadding < l {\n\t\t\t\tstatusPadding = l\n\t\t\t}\n\t\t}\n\t}\n\n\tif len(w.eventIDs) > int(ws.Height)-2 {\n\t\tw.skipChildEvents = true\n\t}\n\tfor _, v := range w.eventIDs {\n\t\tevent := w.events[v]\n\t\tline, lines := w.lineText(event, withID, int(ws.Width), statusPadding)\n\t\tioutil.Print(w.out, line)\n\t\tnumLines += lines\n\t\tfor _, child := range event.children {\n\t\t\tif w.skipChildEvents {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tline, lines = w.lineText(child, false, int(ws.Width), statusPadding)\n\t\t\tioutil.Print(w.out, line)\n\t\t\tnumLines += lines\n\t\t}\n\t}\n\n\tfor i := numLines; i < w.numLines; i++ {\n\t\tif numLines < int(ws.Height)-2 {\n\t\t\tioutil.Println(w.out, aec.EraseLine(aec.EraseModes.All).String())\n\t\t\tnumLines++\n\t\t}\n\t}\n\tw.numLines = numLines\n}\n\nvar percentChars = strings.Split(\"⠀⡀⣀⣄⣤⣦⣶⣷⣿\", \"\") //nolint:gochecknoglobals // constant names\n\nfunc (w *ttyWriter) lineText(event *Event, withID bool, terminalWidth, statusPadding int) (string, int) {\n\tvar (\n\t\thideDetails bool\n\t\ttotal       int64\n\t\tcurrent     int64\n\t\tcompletion  []string\n\t)\n\n\t// only show the aggregated progress while the root operation is in progress\n\tif event.Status == EventStatusWorking {\n\t\tfor _, child := range event.children {\n\t\t\tif child.Status == EventStatusWorking && child.Total == 0 {\n\t\t\t\t// we don't have totals available for all the child events\n\t\t\t\t// so don't show the total progress yet\n\t\t\t\thideDetails = true\n\t\t\t}\n\t\t\ttotal += child.Total\n\t\t\tcurrent += child.Current\n\t\t\tif child.Percent > 0 {\n\t\t\t\tcompletion = append(completion, percentChars[(len(percentChars)-1)*child.Percent/100])\n\t\t\t}\n\t\t}\n\t}\n\n\t// don't try to show detailed progress if we don't have any idea\n\tif total == 0 {\n\t\thideDetails = true\n\t}\n\n\tvar txt string\n\tif len(completion) > 0 {\n\t\tvar details string\n\t\tif !hideDetails {\n\t\t\tdetails = fmt.Sprintf(\" %7s / %-7s \", units.HumanSize(float64(current)), units.HumanSize(float64(total)))\n\t\t}\n\t\ttxt = fmt.Sprintf(\"[%s]%s%s\",\n\t\t\tsuccessColor.Apply(strings.Join(completion, \"\")),\n\t\t\tdetails,\n\t\t\tevent.Text,\n\t\t)\n\t} else {\n\t\ttxt = event.Text\n\t}\n\tif withID {\n\t\tif txt == \"\" {\n\t\t\ttxt = event.ID\n\t\t} else {\n\t\t\ttxt = fmt.Sprintf(\"%s %s\", event.ID, txt)\n\t\t}\n\t}\n\ttextLen := len(txt)\n\tpadding := statusPadding - textLen\n\tif padding < 0 {\n\t\tpadding = 0\n\t}\n\tif txt != \"\" && padding == 0 {\n\t\tpadding++\n\t}\n\n\t// calculate the max length for the status text\n\tconst spinnerWidth = 3 // spinner surrounded by space\n\tallExceptStatusLen := spinnerWidth + textLen + padding\n\tvar timerLen int\n\tvar timer, coloredTimer string\n\tswitch {\n\tcase event.Status == EventStatusWorking:\n\t\ttimer = fmt.Sprintf(\"%.1fs \", time.Since(event.StartTime).Seconds())\n\tcase !event.EndTime.IsZero():\n\t\ttimer = fmt.Sprintf(\"%.1fs \", event.EndTime.Sub(event.StartTime).Seconds())\n\tdefault:\n\t\ttimer = \"\"\n\t}\n\n\tif timer != \"\" {\n\t\ttimerLen = len(timer)\n\t\tcoloredTimer = timerColor.Apply(timer)\n\t\tallExceptStatusLen += timerLen\n\t}\n\n\tmaxStatusLen := terminalWidth - allExceptStatusLen - 1 //\n\tif maxStatusLen < 5 {\n\t\t// This will look weird, and that's intentional when terminalWidth < 5 + allExceptStatusLen\n\t\tmaxStatusLen = math.MaxInt\n\t}\n\tlines := strings.Split(wordwrap.WrapString(event.StatusText, uint(maxStatusLen)), \"\\n\")\n\n\tbld := &strings.Builder{}\n\tfor li, status := range lines {\n\t\tif li > 0 {\n\t\t\twritePad(bld, spinnerWidth+textLen)\n\t\t\ttimerLen = 0\n\t\t} else {\n\t\t\tbld.WriteByte(' ')\n\t\t\tbld.WriteString(event.Spinner())\n\t\t\tbld.WriteByte(' ')\n\t\t\tbld.WriteString(txt)\n\t\t}\n\t\tif len(status) > 0 || timerLen > 0 {\n\t\t\twritePad(bld, padding)\n\t\t\tbld.WriteString(event.Status.color().Apply(status))\n\t\t\tif timerLen > 0 {\n\t\t\t\twritePad(bld, terminalWidth-allExceptStatusLen-len(status))\n\t\t\t\tbld.WriteString(coloredTimer)\n\t\t\t}\n\t\t}\n\t\tbld.WriteString(aec.EraseLine(aec.EraseModes.Tail).String())\n\t\tbld.WriteByte('\\n')\n\t}\n\treturn bld.String(), len(lines)\n}\n\nfunc writePad(bld *strings.Builder, padLen int) {\n\tfor ; padLen > 0; padLen-- {\n\t\tbld.WriteByte(' ')\n\t}\n}\n\nfunc numDone(events map[string]*Event) int {\n\ti := 0\n\tfor _, e := range events {\n\t\tif e.Status != EventStatusWorking {\n\t\t\ti++\n\t\t}\n\t}\n\treturn i\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/tty_test.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestLineText(t *testing.T) {\n\tnow := time.Now()\n\tev := &Event{\n\t\tID:         \"id\",\n\t\tText:       \"Text\",\n\t\tStatus:     EventStatusWorking,\n\t\tStatusText: \"Status\",\n\t\tEndTime:    now,\n\t\tStartTime:  now,\n\t\tspinner: &spinner{\n\t\t\tchars: []string{\".\"},\n\t\t},\n\t}\n\n\tlineWidth := len(ev.Text)\n\n\tout, n := tty().lineText(ev, false, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[33m.\\x1b[0m Text Status                               \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n\n\tev.Status = EventStatusDone\n\tout, n = tty().lineText(ev, false, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[32m✔\\x1b[0m Text \\x1b[32mStatus\\x1b[0m                               \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n\n\tev.Status = EventStatusError\n\tout, n = tty().lineText(ev, false, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[31m\\x1b[1m✘\\x1b[0m Text \\x1b[31m\\x1b[1mStatus\\x1b[0m                               \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n\n\tev.Status = EventStatusWarning\n\tout, n = tty().lineText(ev, false, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[33m\\x1b[1m!\\x1b[0m Text \\x1b[33m\\x1b[1mStatus\\x1b[0m                               \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n\n\tev.Status = EventStatusWorking\n\tev.Text = \"\"\n\tout, n = tty().lineText(ev, false, 50, 0)\n\tassert.Equal(t, \" \\x1b[33m.\\x1b[0m Status                                    \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n\n\tev.Text = \"Text\"\n\tlineWidth = len(fmt.Sprintf(\"%s %s \", ev.ID, ev.Text))\n\n\tout, n = tty().lineText(ev, true, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[33m.\\x1b[0m id Text Status                            \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n\n\tev.Status = EventStatusDone\n\tout, n = tty().lineText(ev, true, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[32m✔\\x1b[0m id Text \\x1b[32mStatus\\x1b[0m                            \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n\n\tev.Status = EventStatusError\n\tout, n = tty().lineText(ev, true, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[31m\\x1b[1m✘\\x1b[0m id Text \\x1b[31m\\x1b[1mStatus\\x1b[0m                            \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n\n\tev.Status = EventStatusWarning\n\tout, n = tty().lineText(ev, true, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[33m\\x1b[1m!\\x1b[0m id Text \\x1b[33m\\x1b[1mStatus\\x1b[0m                            \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n}\n\nfunc TestEventTruncate(t *testing.T) {\n\tnow := time.Now()\n\tev := &Event{\n\t\tID:         \"id\",\n\t\tText:       \"Text\",\n\t\tStatus:     EventStatusWorking,\n\t\tStatusText: \"Long status text that should be truncated\",\n\t\tEndTime:    now,\n\t\tStartTime:  now,\n\t\tspinner: &spinner{\n\t\t\tchars: []string{\".\"},\n\t\t},\n\t}\n\n\tlineWidth := len(fmt.Sprintf(\"%s %s \", ev.ID, ev.Text))\n\tout, n := tty().lineText(ev, true, 40, lineWidth)\n\tassert.Equal(t, \" \\x1b[33m.\\x1b[0m id Text Long status text that   \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n           should be truncated\\x1b[0K\\n\", out)\n\tassert.Equal(t, 2, n)\n\n\tev.Status = EventStatusDone\n\tout, n = tty().lineText(ev, true, 40, lineWidth)\n\tassert.Equal(t, \" \\x1b[32m✔\\x1b[0m id Text \\x1b[32mLong status text that\\x1b[0m   \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n           \\x1b[32mshould be truncated\\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 2, n)\n}\n\nfunc TestErrorEventWrap(t *testing.T) {\n\tnow := time.Now()\n\tev := &Event{\n\t\tID:         \"id\",\n\t\tText:       \"Text\",\n\t\tStatus:     EventStatusError,\n\t\tStatusText: \"Long status text that should be wrapped\",\n\t\tEndTime:    now,\n\t\tStartTime:  now,\n\t\tspinner: &spinner{\n\t\t\tchars: []string{\".\"},\n\t\t},\n\t}\n\n\tlineWidth := len(fmt.Sprintf(\"%s %s \", ev.ID, ev.Text))\n\tout, n := tty().lineText(ev, true, 40, lineWidth)\n\tassert.Equal(t, \" \\x1b[31m\\x1b[1m✘\\x1b[0m id Text \\x1b[31m\\x1b[1mLong status text that\\x1b[0m   \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n           \\x1b[31m\\x1b[1mshould be wrapped\\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 2, n)\n}\n\nfunc TestLineTextSingleEvent(t *testing.T) {\n\tnow := time.Now()\n\tev := &Event{\n\t\tID:         \"id\",\n\t\tText:       \"Text\",\n\t\tStatus:     EventStatusDone,\n\t\tStatusText: \"Status\",\n\t\tStartTime:  now,\n\t\tEndTime:    now,\n\t\tspinner: &spinner{\n\t\t\tchars: []string{\".\"},\n\t\t},\n\t}\n\n\tlineWidth := len(fmt.Sprintf(\"%s %s\", ev.ID, ev.Text))\n\n\tout, n := tty().lineText(ev, true, 50, lineWidth)\n\tassert.Equal(t, \" \\x1b[32m✔\\x1b[0m id Text \\x1b[32mStatus\\x1b[0m                            \\x1b[34m0.0s \\x1b[0m\\x1b[0K\\n\", out)\n\tassert.Equal(t, 1, n)\n}\n\nfunc TestErrorEvent(t *testing.T) {\n\tw := tty()\n\te := &Event{\n\t\tID:         \"id\",\n\t\tText:       \"Text\",\n\t\tStatus:     EventStatusWorking,\n\t\tStatusText: \"Working\",\n\t\tStartTime:  time.Now().Add(-1 * time.Second),\n\t\tspinner: &spinner{\n\t\t\tchars: []string{\".\"},\n\t\t},\n\t}\n\t// Fire \"Working\" event and check end time isn't touched\n\tw.Write(e)\n\tevent, ok := w.events[e.ID]\n\tassert.True(t, ok)\n\tassert.True(t, event.EndTime.Equal(time.Time{}))\n\n\t// Fire \"Error\" event and check end time is set\n\te = &Event{\n\t\tID:         \"id\",\n\t\tText:       \"Text\",\n\t\tStatus:     EventStatusError,\n\t\tStatusText: \"Working\",\n\t\tStartTime:  time.Now(),\n\t\tspinner: &spinner{\n\t\t\tchars: []string{\".\"},\n\t\t},\n\t}\n\tw.Write(e)\n\tevent, ok = w.events[e.ID]\n\tassert.True(t, ok)\n\tassert.True(t, event.EndTime.After(event.StartTime))\n}\n\nfunc TestWarningEvent(t *testing.T) {\n\tw := tty()\n\te := &Event{\n\t\tID:         \"id\",\n\t\tText:       \"Text\",\n\t\tStatus:     EventStatusWorking,\n\t\tStatusText: \"Working\",\n\t\tStartTime:  time.Now(),\n\t\tspinner: &spinner{\n\t\t\tchars: []string{\".\"},\n\t\t},\n\t}\n\t// Fire \"Working\" event and check end time isn't touched\n\tw.Write(e)\n\tevent, ok := w.events[e.ID]\n\tassert.True(t, ok)\n\tassert.True(t, event.EndTime.Equal(time.Time{}))\n\n\t// Fire \"Warning\" event and check end time isn't touched\n\te = &Event{\n\t\tID:         \"id\",\n\t\tText:       \"Text\",\n\t\tStatus:     EventStatusWarning,\n\t\tStatusText: \"Working\",\n\t\tStartTime:  time.Now(),\n\t\tspinner: &spinner{\n\t\t\tchars: []string{\".\"},\n\t\t},\n\t}\n\tw.Write(e)\n\tevent, ok = w.events[e.ID]\n\tassert.True(t, ok)\n\tassert.True(t, event.EndTime.Equal(time.Time{}))\n}\n\nfunc tty() *ttyWriter {\n\treturn newTTYWriter(os.Stderr).(*ttyWriter)\n}\n"
  },
  {
    "path": "pkg/client/cli/progress/writer.go",
    "content": "/*\n   Copyright 2020 Docker Compose CLI authors\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n*/\n\npackage progress\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"github.com/moby/term\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n)\n\n// Writer can write multiple progress events.\ntype Writer interface {\n\t// Start will start the writer in a new goroutine\n\tStart(ctx context.Context, progressTitle string)\n\n\t// Stop stops a started spinner\n\tStop()\n\n\t// IsNoOp returns true for the noop spinner, false for every other spinner.\n\tIsNoOp() bool\n\n\t// Write writes progress events. If multiple events are given and the writer permits it, they will\n\t// end up in different spinners and be reported individually based on their ID.\n\tWrite(...*Event)\n\n\t// TriggerRefresh triggers a refresh of event output on the tty writer\n\tTriggerRefresh()\n}\n\ntype writerKey struct{}\n\n// WithContextWriter adds the writer to the context.\nfunc WithContextWriter(ctx context.Context, writer Writer) context.Context {\n\treturn context.WithValue(ctx, writerKey{}, writer)\n}\n\n// ContextWriter returns the writer from the context.\nfunc ContextWriter(ctx context.Context) Writer {\n\ts, ok := ctx.Value(writerKey{}).(Writer)\n\tif !ok {\n\t\treturn noopWriter{}\n\t}\n\treturn s\n}\n\ntype eventIdKey struct{}\n\nfunc WithEventId(ctx context.Context, id string) context.Context {\n\tcurrentID := EventId(ctx)\n\tif currentID == id {\n\t\treturn ctx\n\t}\n\treturn context.WithValue(ctx, eventIdKey{}, id)\n}\n\nfunc EventId(ctx context.Context) string {\n\tid, ok := ctx.Value(eventIdKey{}).(string)\n\tif !ok {\n\t\treturn \"\"\n\t}\n\treturn id\n}\n\nfunc IsNoOp(ctx context.Context) bool {\n\treturn ContextWriter(ctx).IsNoOp()\n}\n\nfunc Start(ctx context.Context, title string) {\n\tw := ContextWriter(ctx)\n\tw.Stop()\n\tw.Start(ctx, title)\n}\n\nfunc Stop(ctx context.Context) {\n\tContextWriter(ctx).Stop()\n}\n\nfunc Working(ctx context.Context, args ...any) *Event {\n\treturn write(ctx, EventStatusWorking, false, args)\n}\n\nfunc Workingf(ctx context.Context, format string, args ...any) *Event {\n\treturn writef(ctx, EventStatusWorking, false, format, args)\n}\n\nfunc Done(ctx context.Context, args ...any) *Event {\n\treturn write(ctx, EventStatusDone, false, args)\n}\n\nfunc Donef(ctx context.Context, format string, args ...any) *Event {\n\treturn writef(ctx, EventStatusDone, false, format, args)\n}\n\n// PrintDone is like Done but also enforces that the plain writer will print the output. The\n// plain writer normally skips Working and Done events.\nfunc PrintDone(ctx context.Context, args ...any) *Event {\n\treturn write(ctx, EventStatusDone, true, args)\n}\n\n// PrintDonef is like Donef but also enforces that the plain writer will print the output. The\n// plain writer normally skips Working and Done events.\nfunc PrintDonef(ctx context.Context, format string, args ...any) *Event {\n\treturn writef(ctx, EventStatusDone, true, format, args)\n}\n\nfunc Info(ctx context.Context, args ...any) *Event {\n\treturn write(ctx, EventStatusInfo, false, args)\n}\n\nfunc Infof(ctx context.Context, format string, args ...any) *Event {\n\treturn writef(ctx, EventStatusInfo, false, format, args)\n}\n\nfunc Error(ctx context.Context, args ...any) *Event {\n\treturn write(ctx, EventStatusError, false, args)\n}\n\nfunc Errorf(ctx context.Context, format string, args ...any) *Event {\n\treturn writef(ctx, EventStatusError, false, format, args)\n}\n\nfunc Warning(ctx context.Context, args ...any) *Event {\n\treturn write(ctx, EventStatusWarning, false, args)\n}\n\nfunc Warningf(ctx context.Context, format string, args ...any) *Event {\n\treturn writef(ctx, EventStatusWarning, false, format, args)\n}\n\nfunc write(ctx context.Context, status EventStatus, plain bool, args []any) *Event {\n\tev := NewEvent(EventId(ctx), status, fmt.Sprint(args...))\n\tev.plainAlways = plain\n\tContextWriter(ctx).Write(ev)\n\treturn ev\n}\n\nfunc writef(ctx context.Context, status EventStatus, plain bool, format string, args []any) *Event {\n\tev := NewEvent(EventId(ctx), status, fmt.Sprintf(format, args...))\n\tev.plainAlways = plain\n\tContextWriter(ctx).Write(ev)\n\treturn ev\n}\n\nfunc Write(ctx context.Context, events ...*Event) {\n\tContextWriter(ctx).Write(events...)\n}\n\nfunc MaybeWriteError(ctx context.Context, err error) error {\n\tif err != nil && errcat.GetCategory(err) != errcat.Silent {\n\t\tError(ctx, err.Error())\n\t\terr = errcat.Silent.New(err)\n\t}\n\treturn err\n}\n\ntype Mode string\n\nconst (\n\t// ModeAuto detect console capabilities.\n\tModeAuto = Mode(\"auto\")\n\t// ModeTTY use terminal capability for advanced rendering.\n\tModeTTY = Mode(\"tty\")\n\t// ModePlain dump raw events to output.\n\tModePlain = Mode(\"plain\")\n\t// ModeQuiet don't display events.\n\tModeQuiet = Mode(\"quiet\")\n\t// ModeJSON outputs a machine-readable JSON stream.\n\tModeJSON = Mode(\"json\")\n)\n\n// NewWriter returns a new multi-progress writer.\nfunc NewWriter(out, err io.Writer, mode Mode) Writer {\n\t_, isTerminal := term.GetFdInfo(out)\n\tif mode == ModeQuiet {\n\t\treturn quiet{}\n\t}\n\n\ttty := mode == ModeTTY\n\tif mode == ModeAuto && isTerminal {\n\t\ttty = true\n\t}\n\tif tty {\n\t\treturn newTTYWriter(err)\n\t}\n\tif mode == ModeJSON {\n\t\treturn &jsonWriter{\n\t\t\tout: out,\n\t\t}\n\t}\n\treturn plainWriter{\n\t\tout: out,\n\t\terr: err,\n\t}\n}\n"
  },
  {
    "path": "pkg/client/cmd_error.go",
    "content": "package client\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os/exec\"\n)\n\n// RunError checks if the given err is a *exit.ExitError, and if so, extracts\n// Stderr and the ExitCode from it.\nfunc RunError(err error) error {\n\tvar ee *exec.ExitError\n\tif errors.As(err, &ee) {\n\t\tif len(ee.Stderr) > 0 {\n\t\t\terr = fmt.Errorf(\"%s, exit code %d\", string(ee.Stderr), ee.ExitCode())\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"exit code %d\", ee.ExitCode())\n\t\t}\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/config.go",
    "content": "package client\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"log/slog\"\n\t\"math\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n\t\"google.golang.org/protobuf/types/known/durationpb\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\tjson2 \"github.com/telepresenceio/telepresence/v2/pkg/json\"\n)\n\ntype DefaultsAware interface {\n\tdefaults() DefaultsAware\n\tIsZero() bool\n\tMarshalJSONTo(out *jsontext.Encoder) error\n\tUnmarshalJSONFrom(in *jsontext.Decoder) error\n}\n\nfunc jsonName(f reflect.StructField) string {\n\tif !f.IsExported() {\n\t\treturn \"\"\n\t}\n\tif jt := f.Tag.Get(\"json\"); len(jt) > 0 {\n\t\tif jt == \"-\" {\n\t\t\treturn \"\"\n\t\t}\n\t\tif ci := strings.IndexByte(jt, ','); ci > 0 {\n\t\t\treturn jt[:ci]\n\t\t}\n\t\treturn jt\n\t}\n\treturn f.Name\n}\n\n// mapWithoutDefaults returns a map with all values in the given struct that are not equal to their corresponding default value.\nfunc mapWithoutDefaults[T DefaultsAware](sourceStruct T) map[string]any {\n\tm := make(map[string]any)\n\tsv := reflect.ValueOf(sourceStruct).Elem()\n\tdv := reflect.ValueOf(sourceStruct.defaults()).Elem()\n\tvt := sv.Type()\n\tfor _, f := range reflect.VisibleFields(vt) {\n\t\tif n := jsonName(f); n != \"\" {\n\t\t\tsf := sv.FieldByIndex(f.Index).Interface()\n\t\t\tif !reflect.DeepEqual(sf, dv.FieldByIndex(f.Index).Interface()) {\n\t\t\t\tm[n] = sf\n\t\t\t}\n\t\t}\n\t}\n\treturn m\n}\n\n// mapWithoutDefaults will merge non-default values from sourceStruct into targetStruct.\nfunc mergeNonDefaults[T DefaultsAware](targetStruct T, sourceStruct T) {\n\ttv := reflect.ValueOf(targetStruct).Elem()\n\tsv := reflect.ValueOf(sourceStruct).Elem()\n\tdv := reflect.ValueOf(targetStruct.defaults()).Elem()\n\tvt := tv.Type()\n\tfor _, f := range reflect.VisibleFields(vt) {\n\t\tif jsonName(f) != \"\" {\n\t\t\tsf := sv.FieldByIndex(f.Index)\n\t\t\tif !reflect.DeepEqual(sf.Interface(), dv.FieldByIndex(f.Index).Interface()) {\n\t\t\t\ttv.FieldByIndex(f.Index).Set(sf)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// isDefault returns true if the given struct is equal to its default.\nfunc isDefault[T DefaultsAware](sourceStruct T) bool {\n\treturn reflect.DeepEqual(sourceStruct, sourceStruct.defaults())\n}\n\nconst ConfigFile = \"config.yml\"\n\ntype Config interface {\n\tfmt.Stringer\n\tBase() *config\n\tCluster() *Cluster\n\tDNS() *DNS\n\tDocker() *Docker\n\tGrpc() *Grpc\n\tHelm() *Helm\n\tIntercept() *Intercept\n\tImages() *Images\n\tLogLevels() *LogLevels\n\tRouting() *Routing\n\tTimeouts() *Timeouts\n\n\tMarshalYAML() ([]byte, error)\n\tOSSpecific() *OSSpecificConfig\n\tDestructiveMerge(Config)\n\tMerge(priority Config) Config\n}\n\n// config contains all configuration values for the telepresence CLI.\ntype config struct {\n\tOSSpecificConfig ``\n\tClusterV         Cluster   `json:\"cluster,omitzero\"`\n\tDNSV             DNS       `json:\"dns,omitzero\"`\n\tDockerV          Docker    `json:\"docker,omitzero\"`\n\tGrpcV            Grpc      `json:\"grpc,omitzero\"`\n\tHelmV            Helm      `json:\"helm,omitzero\"`\n\tImagesV          Images    `json:\"images,omitzero\"`\n\tInterceptV       Intercept `json:\"intercept,omitzero\"`\n\tLogLevelsV       LogLevels `json:\"logLevels,omitzero\"`\n\tRoutingV         Routing   `json:\"routing,omitzero\"`\n\tTimeoutsV        Timeouts  `json:\"timeouts,omitzero\"`\n}\n\nfunc (c *config) OSSpecific() *OSSpecificConfig {\n\treturn &c.OSSpecificConfig\n}\n\nfunc (c *config) Base() *config {\n\treturn c\n}\n\nfunc (c *config) Timeouts() *Timeouts {\n\treturn &c.TimeoutsV\n}\n\nfunc (c *config) LogLevels() *LogLevels {\n\treturn &c.LogLevelsV\n}\n\nfunc (c *config) Images() *Images {\n\treturn &c.ImagesV\n}\n\nfunc (c *config) Grpc() *Grpc {\n\treturn &c.GrpcV\n}\n\nfunc (c *config) Intercept() *Intercept {\n\treturn &c.InterceptV\n}\n\nfunc (c *config) Cluster() *Cluster {\n\treturn &c.ClusterV\n}\n\nfunc (c *config) Docker() *Docker {\n\treturn &c.DockerV\n}\n\nfunc (c *config) DNS() *DNS {\n\treturn &c.DNSV\n}\n\nfunc (c *config) Routing() *Routing {\n\treturn &c.RoutingV\n}\n\nfunc (c *config) Helm() *Helm {\n\treturn &c.HelmV\n}\n\nfunc (c *config) MarshalYAML() ([]byte, error) {\n\tdata, err := json2.Marshal(c)\n\tif err == nil {\n\t\tdata, err = yaml.JSONToYAML(data)\n\t}\n\treturn data, err\n}\n\nfunc UnmarshalJSONConfig(data []byte, rejectUnknown bool) (Config, error) {\n\tcfg := GetDefaultConfig()\n\tif err := json2.Unmarshal(data, cfg, rejectUnknown); err != nil {\n\t\treturn nil, err\n\t}\n\treturn cfg, nil\n}\n\nfunc ParseConfigYAML(ctx context.Context, path string, data []byte) (Config, error) {\n\tdata = bytes.TrimSpace(data)\n\tif len(data) == 0 {\n\t\treturn GetDefaultConfig(), nil\n\t}\n\tdata, err := yaml.YAMLToJSON(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcfg, err := UnmarshalJSONConfig(data, true)\n\tif err != nil {\n\t\tvar semanticErr *json.SemanticError\n\t\tif errors.As(err, &semanticErr) && strings.Contains(semanticErr.Error(), \"unknown object member name \") {\n\t\t\ts := semanticErr.Error()\n\t\t\t// Strip unnecessarily verbose text from the message, but retain the type.\n\t\t\tif m := regexp.MustCompile(`json:.+ of type (.*)$`).FindStringSubmatch(s); len(m) == 2 {\n\t\t\t\ts = m[1]\n\t\t\t}\n\t\t\tclog.Errorf(ctx, \"%s: %v\", path, s)\n\t\t\tcfg, err = UnmarshalJSONConfig(data, false)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tif cfg.Timeouts().PrivateTrafficAgentArrival != 0 {\n\t\tclog.Warnf(ctx, \"please use Helm chart setting timeouts.agentArrival instead of deprecated timeouts.trafficAgentArrival\")\n\t}\n\tif cfg.Routing().VirtualSubnet == defaultVirtualSubnet && cfg.Cluster().OldVirtualIPSubnet != \"\" {\n\t\tclog.Warnf(ctx, \"please use routing.VirtualSubnet instead of deprecated deprecated cluster.VirtualIPSubnet\")\n\t\tsn, err := netip.ParsePrefix(cfg.Cluster().OldVirtualIPSubnet)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse deprecated cluster.VirtualIPSubnet: %w\", err)\n\t\t}\n\t\tcfg.Routing().VirtualSubnet = sn\n\t}\n\tif r := cfg.Routing(); r.RecursionBlockDuration > 0 || r.RecursionBlockTreads > 0 {\n\t\tclog.Warnf(ctx, \"routing.recursionBlockDuration and routing.recursionBlockTreads are deprecated and no longer used; \"+\n\t\t\t\"use the route-controller DaemonSet instead (see docs/reference/route-controller.md)\")\n\t}\n\treturn cfg, nil\n}\n\n// DestructiveMerge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (c *config) DestructiveMerge(lc Config) {\n\tc.OSSpecificConfig.Merge(lc.OSSpecific())\n\tc.TimeoutsV.merge(lc.Timeouts())\n\tc.LogLevelsV.merge(lc.LogLevels())\n\tc.ImagesV.merge(lc.Images())\n\tc.GrpcV.merge(lc.Grpc())\n\tc.InterceptV.merge(lc.Intercept())\n\tc.ClusterV.merge(lc.Cluster())\n\tc.DockerV.merge(lc.Docker())\n\tc.DNSV.merge(lc.DNS())\n\tc.RoutingV.merge(lc.Routing())\n\tc.HelmV.merge(lc.Helm())\n}\n\nfunc (c *config) Merge(lc Config) Config {\n\tcfg := getDefaultConfig()\n\t*cfg = *c\n\tcfg.DestructiveMerge(lc)\n\treturn cfg\n}\n\nfunc (c *config) String() string {\n\ty, _ := c.MarshalYAML()\n\treturn string(y)\n}\n\n// WatchConfig uses a file system watcher that receives events when the configuration changes\n// and calls the given function when that happens.\nfunc WatchConfig(c context.Context, onReload func(context.Context) error) error {\n\tconfigFile := GetConfigFile(c)\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer watcher.Close()\n\n\t// The directory containing the config file must be watched because editing\n\t// the file will typically end with renaming the original and then creating\n\t// a new file. A watcher that follows the inode will not see when the new\n\t// file is created.\n\tif err = watcher.Add(filepath.Dir(configFile)); err != nil {\n\t\treturn err\n\t}\n\n\t// The delay timer will initially sleep forever. It's reset to a very short\n\t// delay when the file is modified.\n\tdelay := time.AfterFunc(time.Duration(math.MaxInt64), func() {\n\t\tcfg, err := LoadConfig(c)\n\t\tif err != nil {\n\t\t\tclog.Error(c, err)\n\t\t} else {\n\t\t\tReplaceConfig(c, cfg)\n\t\t\terr = onReload(c)\n\t\t}\n\t\tif err != nil {\n\t\t\tclog.Error(c, err)\n\t\t}\n\t})\n\tdefer delay.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-c.Done():\n\t\t\treturn nil\n\t\tcase err = <-watcher.Errors:\n\t\t\tclog.Error(c, err)\n\t\tcase event := <-watcher.Events:\n\t\t\tif event.Op&(fsnotify.Write|fsnotify.Create) != 0 && event.Name == configFile {\n\t\t\t\t// The config file was created or modified. Let's defer the load just a little bit\n\t\t\t\t// in case there are more modifications (a write out with vi will typically cause\n\t\t\t\t// one CREATE event and at least one WRITE event).\n\t\t\t\tdelay.Reset(5 * time.Millisecond)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype Timeouts struct {\n\t// These all nave names starting with \"Private\" because we \"want\" them to be unexported to force you to\n\t// use .TimeoutContext(), but we dont' want them to be hidden from the JSON/YAML engines.\n\n\tPrivateClusterConnect        time.Duration `json:\"clusterConnect,format:units\"`\n\tPrivateConnectivityCheck     time.Duration `json:\"connectivityCheck,format:units\"`\n\tPrivateEndpointDial          time.Duration `json:\"endpointDial,format:units\"`\n\tPrivateHelm                  time.Duration `json:\"helm,format:units\"`\n\tPrivateIntercept             time.Duration `json:\"intercept,format:units\"`\n\tPrivateRoundtripLatency      time.Duration `json:\"roundtripLatency,format:units\"`\n\tPrivateProxyDial             time.Duration `json:\"proxyDial,format:units\"`\n\tPrivateTrafficManagerAPI     time.Duration `json:\"trafficManagerAPI,format:units\"`\n\tPrivateTrafficManagerConnect time.Duration `json:\"trafficManagerConnect,format:units\"`\n\tPrivateTrafficAgentArrival   time.Duration `json:\"trafficAgentArrival,format:units\"` // Deprecated.\n\tPrivateFtpReadWrite          time.Duration `json:\"ftpReadWrite,format:units\"`\n\tPrivateFtpShutdown           time.Duration `json:\"ftpShutdown,format:units\"`\n\tPrivateContainerShutdown     time.Duration `json:\"containerShutdown,format:units\"`\n}\n\ntype TimeoutID int\n\nconst (\n\t// TimeoutClusterConnect is the maximum time to wait for a connection to the cluster to be established.\n\tTimeoutClusterConnect TimeoutID = iota\n\n\t// TimeoutConnectivityCheck timeout used when checking if the cluster is already proxied on the workstation.\n\tTimeoutConnectivityCheck\n\n\t// TimeoutEndpointDial is how long to wait for a Dial to a service for which the IP is known.\n\tTimeoutEndpointDial\n\n\t// TimeoutHelm is how long to wait for any helm operation.\n\tTimeoutHelm\n\n\t// TimeoutIntercept is the time to wait for an intercept after the agents has been installed.\n\tTimeoutIntercept\n\n\t// TimeoutProxyDial is how long to wait for the proxy to establish an outbound connection.\n\tTimeoutProxyDial\n\n\t// TimeoutRoundtripLatency is how much to add to the EndpointDial timeout when establishing a remote connection.\n\tTimeoutRoundtripLatency\n\n\t// TimeoutTrafficManagerAPI is how long to wait for the traffic-manager API to connect.\n\tTimeoutTrafficManagerAPI\n\n\t// TimeoutTrafficManagerConnect is how long to wait for the initial port-forwards to the traffic-manager.\n\tTimeoutTrafficManagerConnect\n\n\t// TimeoutFtpReadWrite read/write timeout used by the fuseftp client.\n\tTimeoutFtpReadWrite\n\n\t// TimeoutFtpShutdown max time to wait for the fuseftp client to complete pending operations before forcing termination.\n\tTimeoutFtpShutdown\n\n\t// TimeoutContainerShutdown max time to wait for a docker container to stop before forcing termination.\n\tTimeoutContainerShutdown\n)\n\ntype timeoutContext struct {\n\tcontext.Context\n\ttimeoutID  TimeoutID\n\ttimeoutVal time.Duration\n}\n\nfunc (ctx *timeoutContext) Err() error {\n\terr := ctx.Context.Err()\n\tif errors.Is(err, context.DeadlineExceeded) {\n\t\terr = timeoutError{\n\t\t\ttimeoutID:  ctx.timeoutID,\n\t\t\ttimeoutVal: ctx.timeoutVal,\n\t\t\tconfigFile: GetConfigFile(ctx),\n\t\t\terr:        err,\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (t *Timeouts) Get(timeoutID TimeoutID) time.Duration {\n\tvar timeoutVal time.Duration\n\tswitch timeoutID {\n\tcase TimeoutClusterConnect:\n\t\ttimeoutVal = t.PrivateClusterConnect\n\tcase TimeoutConnectivityCheck:\n\t\ttimeoutVal = t.PrivateConnectivityCheck\n\tcase TimeoutEndpointDial:\n\t\ttimeoutVal = t.PrivateEndpointDial\n\tcase TimeoutHelm:\n\t\ttimeoutVal = t.PrivateHelm\n\tcase TimeoutIntercept:\n\t\ttimeoutVal = t.PrivateIntercept\n\tcase TimeoutProxyDial:\n\t\ttimeoutVal = t.PrivateProxyDial\n\tcase TimeoutRoundtripLatency:\n\t\ttimeoutVal = t.PrivateRoundtripLatency\n\tcase TimeoutTrafficManagerAPI:\n\t\ttimeoutVal = t.PrivateTrafficManagerAPI\n\tcase TimeoutTrafficManagerConnect:\n\t\ttimeoutVal = t.PrivateTrafficManagerConnect\n\tcase TimeoutFtpReadWrite:\n\t\ttimeoutVal = t.PrivateFtpReadWrite\n\tcase TimeoutFtpShutdown:\n\t\ttimeoutVal = t.PrivateFtpShutdown\n\tcase TimeoutContainerShutdown:\n\t\ttimeoutVal = t.PrivateContainerShutdown\n\tdefault:\n\t\tpanic(\"invalid TimeoutID\")\n\t}\n\treturn timeoutVal\n}\n\n// TimeoutContext returns a context with the timeout that is configured for the given timeoutID and a cancel function.\n// The context will be canceled if:\n//\n//   - the timeout is reached\n//   - the cancel function is called\n//   - the parent context is canceled\nfunc (t *Timeouts) TimeoutContext(ctx context.Context, timeoutID TimeoutID) (context.Context, context.CancelFunc) {\n\ttimeoutVal := t.Get(timeoutID)\n\tctx, cancel := context.WithTimeout(ctx, timeoutVal)\n\treturn &timeoutContext{\n\t\tContext:    ctx,\n\t\ttimeoutID:  timeoutID,\n\t\ttimeoutVal: timeoutVal,\n\t}, cancel\n}\n\ntype timeoutError struct {\n\ttimeoutID  TimeoutID\n\ttimeoutVal time.Duration\n\tconfigFile string\n\terr        error\n}\n\nfunc (e timeoutError) Error() string {\n\tvar yamlName, humanName string\n\tswitch e.timeoutID {\n\tcase TimeoutClusterConnect:\n\t\tyamlName = \"clusterConnect\"\n\t\thumanName = \"cluster connect\"\n\tcase TimeoutConnectivityCheck:\n\t\tyamlName = \"connectivityCheck\"\n\t\thumanName = \"connectivity check\"\n\tcase TimeoutEndpointDial:\n\t\tyamlName = \"endpointDial\"\n\t\thumanName = \"tunnel endpoint dial with known IP\"\n\tcase TimeoutHelm:\n\t\tyamlName = \"helm\"\n\t\thumanName = \"helm operation\"\n\tcase TimeoutIntercept:\n\t\tyamlName = \"intercept\"\n\t\thumanName = \"intercept\"\n\tcase TimeoutProxyDial:\n\t\tyamlName = \"proxyDial\"\n\t\thumanName = \"proxy dial\"\n\tcase TimeoutRoundtripLatency:\n\t\tyamlName = \"roundtripDelay\"\n\t\thumanName = \"additional delay for tunnel roundtrip\"\n\tcase TimeoutTrafficManagerAPI:\n\t\tyamlName = \"trafficManagerAPI\"\n\t\thumanName = \"traffic manager gRPC API\"\n\tcase TimeoutTrafficManagerConnect:\n\t\tyamlName = \"trafficManagerConnect\"\n\t\thumanName = \"port-forward connection to the traffic manager\"\n\tcase TimeoutFtpReadWrite:\n\t\tyamlName = \"ftpReadWrite\"\n\t\thumanName = \"FTP client read/write\"\n\tcase TimeoutFtpShutdown:\n\t\tyamlName = \"ftpShutdown\"\n\t\thumanName = \"FTP client shutdown grace period\"\n\tcase TimeoutContainerShutdown:\n\t\tyamlName = \"containerShutdown\"\n\t\thumanName = \"Docker container shutdown grace period\"\n\tdefault:\n\t\tpanic(\"should not happen\")\n\t}\n\treturn fmt.Sprintf(\"the %s timed out.  The current timeout %s can be configured as %q in %q\",\n\t\thumanName, e.timeoutVal, \"timeouts.\"+yamlName, e.configFile)\n}\n\nfunc (e timeoutError) Unwrap() error {\n\treturn e.err\n}\n\nfunc CheckTimeout(ctx context.Context, err error) error {\n\tif ctxErr := ctx.Err(); ctxErr != nil && (errors.Is(ctxErr, context.DeadlineExceeded) || err == nil) {\n\t\treturn ctxErr\n\t}\n\treturn err\n}\n\nconst (\n\tdefaultTimeoutsClusterConnect        = 20 * time.Second\n\tdefaultTimeoutsConnectivityCheck     = 500 * time.Millisecond\n\tdefaultTimeoutsEndpointDial          = 3 * time.Second\n\tdefaultTimeoutsHelm                  = 30 * time.Second\n\tdefaultTimeoutsIntercept             = 30 * time.Second\n\tdefaultTimeoutsProxyDial             = 5 * time.Second\n\tdefaultTimeoutsRoundtripLatency      = 2 * time.Second\n\tdefaultTimeoutsTrafficManagerAPI     = 15 * time.Second\n\tdefaultTimeoutsTrafficManagerConnect = 60 * time.Second\n\tdefaultTimeoutsFtpReadWrite          = 1 * time.Minute\n\tdefaultTimeoutsFtpShutdown           = 2 * time.Minute\n\tdefaultTimeoutsContainerShutdown     = 0\n\tmaxTimeoutsConnectivityCheck         = 5 * time.Second\n)\n\nvar defaultTimeouts = Timeouts{ //nolint:gochecknoglobals // constant\n\tPrivateClusterConnect:        defaultTimeoutsClusterConnect,\n\tPrivateConnectivityCheck:     defaultTimeoutsConnectivityCheck,\n\tPrivateEndpointDial:          defaultTimeoutsEndpointDial,\n\tPrivateHelm:                  defaultTimeoutsHelm,\n\tPrivateIntercept:             defaultTimeoutsIntercept,\n\tPrivateProxyDial:             defaultTimeoutsProxyDial,\n\tPrivateRoundtripLatency:      defaultTimeoutsRoundtripLatency,\n\tPrivateTrafficManagerAPI:     defaultTimeoutsTrafficManagerAPI,\n\tPrivateTrafficManagerConnect: defaultTimeoutsTrafficManagerConnect,\n\tPrivateFtpReadWrite:          defaultTimeoutsFtpReadWrite,\n\tPrivateFtpShutdown:           defaultTimeoutsFtpShutdown,\n\tPrivateContainerShutdown:     defaultTimeoutsContainerShutdown,\n}\n\nfunc (t *Timeouts) defaults() DefaultsAware {\n\treturn &defaultTimeouts\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (t *Timeouts) merge(o *Timeouts) {\n\tmergeNonDefaults(t, o)\n\tif t.PrivateConnectivityCheck > maxTimeoutsConnectivityCheck {\n\t\tt.PrivateConnectivityCheck = maxTimeoutsConnectivityCheck\n\t}\n}\n\n// IsZero controls whether this element will be included in marshalled output.\nfunc (t *Timeouts) IsZero() bool {\n\treturn t == nil || *t == defaultTimeouts\n}\n\nfunc (t *Timeouts) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(t))\n}\n\nfunc (t *Timeouts) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype timeouts Timeouts\n\twp := (*timeouts)(t)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\nconst (\n\tdefaultLogLevelsCLI            = slog.LevelInfo\n\tdefaultLogLevelsKubeAuthDaemon = slog.LevelInfo\n\tdefaultLogLevelsUserDaemon     = slog.LevelInfo\n\tdefaultLogLevelsRootDaemon     = slog.LevelInfo\n)\n\nvar defaultLogLevels = LogLevels{ //nolint:gochecknoglobals // constant\n\tCLI:            defaultLogLevelsCLI,\n\tKubeAuthDaemon: defaultLogLevelsKubeAuthDaemon,\n\tUserDaemon:     defaultLogLevelsUserDaemon,\n\tRootDaemon:     defaultLogLevelsRootDaemon,\n}\n\ntype LogLevels struct {\n\tCLI            slog.Level `json:\"cli\"`\n\tKubeAuthDaemon slog.Level `json:\"kubeAuthDaemon\"`\n\tUserDaemon     slog.Level `json:\"userDaemon\"`\n\tRootDaemon     slog.Level `json:\"rootDaemon\"`\n}\n\nfunc (ll *LogLevels) defaults() DefaultsAware {\n\treturn &defaultLogLevels\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (ll *LogLevels) merge(o *LogLevels) {\n\tmergeNonDefaults(ll, o)\n}\n\n// IsZero controls whether this element will be included in marshalled output.\nfunc (ll *LogLevels) IsZero() bool {\n\treturn ll == nil || *ll == defaultLogLevels\n}\n\nfunc (ll *LogLevels) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(ll))\n}\n\nfunc (ll *LogLevels) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype logLevels LogLevels\n\twp := (*logLevels)(ll)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\ntype Images struct {\n\tPrivateRegistry        string `json:\"registry\"`\n\tPrivateAgentImage      string `json:\"agentImage\"`\n\tPrivateClientImage     string `json:\"clientImage\"`\n\tPrivateWebhookRegistry string `json:\"webhookRegistry\"`\n}\n\nconst (\n\tdefaultImagesRegistry = \"ghcr.io/telepresenceio\"\n)\n\nvar defaultImages = Images{ //nolint:gochecknoglobals // constant\n\tPrivateRegistry: defaultImagesRegistry,\n}\n\nfunc (img *Images) defaults() DefaultsAware {\n\treturn &defaultImages\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (img *Images) merge(o *Images) {\n\tmergeNonDefaults(img, o)\n}\n\n// IsZero controls whether this element will be included in marshalled output.\nfunc (img *Images) IsZero() bool {\n\treturn img == nil || *img == defaultImages\n}\n\nfunc (img *Images) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(img))\n}\n\nfunc (img *Images) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype images Images\n\twp := (*images)(img)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\nfunc (img *Images) Registry(c context.Context) string {\n\tif img.PrivateRegistry == defaultImagesRegistry {\n\t\tenv := GetEnv(c)\n\t\tif env.Registry != \"\" {\n\t\t\treturn env.Registry\n\t\t}\n\t}\n\treturn img.PrivateRegistry\n}\n\nfunc (img *Images) WebhookRegistry(_ context.Context) string {\n\treturn img.PrivateWebhookRegistry\n}\n\nfunc (img *Images) AgentImage(c context.Context) string {\n\tif img.PrivateAgentImage != \"\" {\n\t\treturn img.PrivateAgentImage\n\t}\n\treturn GetEnv(c).AgentImage\n}\n\nfunc (img *Images) ClientImage(c context.Context) string {\n\tif img.PrivateClientImage != \"\" {\n\t\treturn img.PrivateClientImage\n\t}\n\treturn GetEnv(c).ClientImage\n}\n\ntype Grpc struct {\n\t// MaxReceiveSize is the maximum message size in bytes the client can receive in a gRPC call or stream message.\n\t// Overrides the gRPC default of 4MB.\n\tMaxReceiveSizeV resource.Quantity `json:\"maxReceiveSize\"`\n\n\t// DaemonPort is the port where the containerized daemon exposes its Connector service. It will be exposed to a\n\t// randomly selected port on the host that the Telepresence CLI will connect to. The daemonPort defaults to 4038.\n\tDaemonPort uint16 `json:\"daemonPort\"`\n\n\t// TeleroutePort is the port where the containerized daemon exposes its Teleroute service that the Teleroute\n\t// Docker Network plugin will connect to.\n\tTeleroutePort uint16 `json:\"teleroutePort\"`\n\n\t// SimulateDisconnect can be set to a duration to simulate a disconnect some time after connecting.\n\t// Intended for debugging purposes only.\n\tSimulateDisconnect time.Duration `json:\"simulateDisconnect,format:units\"`\n\n\t// PingInterval is the interval between \"remain\" pings that the client makes to the traffic-manager.\n\tPingInterval time.Duration `json:\"pingInterval,format:units\"`\n\n\t// WatchRetryInterval is the interval between retries that a watcher uses when the gRPC connection to the traffic-manager is lost.\n\tWatchRetryInterval time.Duration `json:\"watchRetryInterval,format:units\"`\n}\n\nvar defaultGrpc = Grpc{ //nolint:gochecknoglobals // constant\n\tDaemonPort:         4038,\n\tTeleroutePort:      4039,\n\tPingInterval:       time.Minute,\n\tWatchRetryInterval: 10 * time.Second,\n}\n\nfunc (g *Grpc) defaults() DefaultsAware {\n\treturn &defaultGrpc\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (g *Grpc) merge(o *Grpc) {\n\tmergeNonDefaults(g, o)\n}\n\n// IsZero controls whether this element will be included in marshalled output.\nfunc (g *Grpc) IsZero() bool {\n\treturn g == nil || *g == defaultGrpc\n}\n\nfunc (g *Grpc) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(g))\n}\n\nfunc (g *Grpc) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype grpc Grpc\n\twp := (*grpc)(g)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\nfunc (g *Grpc) MaxReceiveSize() int64 {\n\tif !g.MaxReceiveSizeV.IsZero() {\n\t\tif mz, ok := g.MaxReceiveSizeV.AsInt64(); ok {\n\t\t\treturn mz\n\t\t}\n\t}\n\treturn 0\n}\n\nvar defaultIntercept = Intercept{ //nolint:gochecknoglobals // constant\n\tMountCompletionDelay: 300 * time.Millisecond,\n}\n\ntype DockerImage struct {\n\tRegistryAPI string `json:\"registryAPI\"`\n\tRegistry    string `json:\"registry\"`\n\tNamespace   string `json:\"namespace\"`\n\tRepository  string `json:\"repository\"`\n\tTag         string `json:\"tag\"`\n}\n\ntype Intercept struct {\n\tDefaultPort          int           `json:\"defaultPort\"`\n\tUseFtp               bool          `json:\"useFtp\"`\n\tMountsRoot           string        `json:\"mountsRoot\"`\n\tMountCompletionDelay time.Duration `json:\"mountCompletionDelay,format:units\"`\n}\n\nfunc (ic *Intercept) defaults() DefaultsAware {\n\treturn &defaultIntercept\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (ic *Intercept) merge(o *Intercept) {\n\tmergeNonDefaults(ic, o)\n}\n\n// IsZero controls whether this element will be included in marshalled output.\nfunc (ic *Intercept) IsZero() bool {\n\treturn ic == nil || *ic == defaultIntercept\n}\n\nfunc (ic *Intercept) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(ic))\n}\n\nfunc (ic *Intercept) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype intercept Intercept\n\twp := (*intercept)(ic)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\ntype Cluster struct {\n\tDefaultManagerNamespace string   `json:\"defaultManagerNamespace\"`\n\tMappedNamespaces        []string `json:\"mappedNamespaces\"`\n\tForceSPDY               bool     `json:\"forceSPDY\"`\n\tAgentPortForward        bool     `json:\"agentPortForward\"`\n\n\t// deprecated, use Routing.VirtualSubnet\n\tOldVirtualIPSubnet string `json:\"virtualIPSubnet\"`\n}\n\n// This is used by a different config -- the k8s_config, which needs to be able to tell if it's overridden at a cluster or environment variable level.\n// Hence, we don't default to \"ambassador\" but to empty, so that it can check that no default has been given.\nconst defaultDefaultManagerNamespace = \"\"\n\nvar defaultCluster = Cluster{ //nolint:gochecknoglobals // constant\n\tDefaultManagerNamespace: defaultDefaultManagerNamespace,\n\tAgentPortForward:        true,\n}\n\nfunc (cc *Cluster) defaults() DefaultsAware {\n\treturn &defaultCluster\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (cc *Cluster) merge(o *Cluster) {\n\tmergeNonDefaults(cc, o)\n}\n\n// IsZero controls whether this element will be included in marshalled output.\nfunc (cc *Cluster) IsZero() bool {\n\treturn cc == nil || isDefault(cc)\n}\n\nfunc (cc *Cluster) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(cc))\n}\n\nfunc (cc *Cluster) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype cluster Cluster\n\twp := (*cluster)(cc)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\ntype Telemount DockerImage\n\nvar defaultTelemount = Telemount{ //nolint:gochecknoglobals // constant\n\tRegistryAPI: \"ghcr.io/v2\",\n\tRegistry:    \"ghcr.io\",\n\tNamespace:   \"telepresenceio\",\n\tRepository:  \"telemount\",\n\tTag:         \"0.3.2\",\n}\n\nfunc (tm *Telemount) defaults() DefaultsAware {\n\treturn &defaultTelemount\n}\n\nfunc (tm *Telemount) IsZero() bool {\n\treturn *tm == defaultTelemount\n}\n\nfunc (tm *Telemount) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(tm))\n}\n\nfunc (tm *Telemount) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype telemount Telemount\n\twp := (*telemount)(tm)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\ntype Teleroute DockerImage\n\nvar defaultTeleroute = Teleroute{ //nolint:gochecknoglobals // constant\n\tRegistryAPI: \"ghcr.io/v2\",\n\tRegistry:    \"ghcr.io\",\n\tNamespace:   \"telepresenceio\",\n\tRepository:  \"teleroute\",\n\tTag:         \"0.4.0\",\n}\n\nfunc (tr *Teleroute) defaults() DefaultsAware {\n\treturn &defaultTeleroute\n}\n\nfunc (tr *Teleroute) IsZero() bool {\n\treturn *tr == defaultTeleroute\n}\n\nfunc (tr *Teleroute) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(tr))\n}\n\nfunc (tr *Teleroute) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype teleroute Teleroute\n\twp := (*teleroute)(tr)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\ntype Docker struct {\n\t// If set, add flag \"--add-host=host.docker.internal:host-gateway\" when starting the containerized daemon container\n\tAddHostGateway bool      `json:\"addHostGateway,omitzero\"`\n\tTelemount      Telemount `json:\"telemount,omitzero\"`\n\tTeleroute      Teleroute `json:\"teleroute,omitzero\"`\n\tHostGateway    string    `json:\"hostGateway,omitzero\"`\n\tEnableIPv4     bool      `json:\"enableIPv4,omitzero\"`\n\tEnableIPv6     bool      `json:\"enableIPv6,omitzero\"`\n}\n\nconst (\n\tDefaultHostGateway = \"host.docker.internal\"\n\tdefaultEnableIPv4  = true\n\tdefaultEnableIPv6  = true\n)\n\nvar defaultDocker = Docker{ //nolint:gochecknoglobals // constant\n\tAddHostGateway: defaultAddHostGateway,\n\tTelemount:      defaultTelemount,\n\tTeleroute:      defaultTeleroute,\n\tHostGateway:    DefaultHostGateway,\n\tEnableIPv4:     defaultEnableIPv4,\n\tEnableIPv6:     defaultEnableIPv6,\n}\n\nfunc (d *Docker) defaults() DefaultsAware {\n\treturn &defaultDocker\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (d *Docker) merge(o *Docker) {\n\tmergeNonDefaults(d, o)\n}\n\n// IsZero controls whether this element will be included in marshaled output.\nfunc (d *Docker) IsZero() bool {\n\treturn d == nil || isDefault(d)\n}\n\nfunc (d *Docker) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(d))\n}\n\nfunc (d *Docker) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype docker Docker\n\twp := (*docker)(d)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\ntype Helm struct {\n\tChartURL string `json:\"chartURL\"`\n}\n\nconst defaultChartURL = \"oci://ghcr.io/telepresenceio/telepresence-oss\"\n\nvar defaultHelm = Helm{ //nolint:gochecknoglobals // constant\n\tChartURL: defaultChartURL,\n}\n\nfunc (d *Helm) defaults() DefaultsAware {\n\treturn &defaultHelm\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (d *Helm) merge(o *Helm) {\n\tmergeNonDefaults(d, o)\n}\n\n// IsZero controls whether this element will be included in marshaled output.\nfunc (d *Helm) IsZero() bool {\n\treturn d == nil || isDefault(d)\n}\n\nfunc (d *Helm) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(d))\n}\n\nfunc (d *Helm) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype helm Helm\n\twp := (*helm)(d)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\ntype Routing struct {\n\tSubnets              []netip.Prefix `json:\"subnets,omitempty\"`\n\tAlsoProxy            []netip.Prefix `json:\"alsoProxySubnets,omitempty\"`\n\tNeverProxy           []netip.Prefix `json:\"neverProxySubnets,omitempty\"`\n\tAllowConflicting     []netip.Prefix `json:\"allowConflictingSubnets,omitempty\"`\n\tVirtualSubnet        netip.Prefix   `json:\"virtualSubnet\"`\n\tAutoResolveConflicts bool           `json:\"autoResolveConflicts\"`\n\tUseTAP               bool           `json:\"useTAP\"`\n\n\t// For backward compatibility.\n\tOldAlsoProxy        []netip.Prefix `json:\"alsoProxy,omitempty\"`\n\tOldNeverProxy       []netip.Prefix `json:\"neverProxy,omitempty\"`\n\tOldAllowConflicting []netip.Prefix `json:\"allowConflicting,omitempty\"`\n\n\t// Deprecated: no longer used. Use the route-controller DaemonSet instead.\n\tRecursionBlockDuration time.Duration `json:\"recursionBlockDuration,omitempty,format:units\"`\n\t// Deprecated: no longer used. Use the route-controller DaemonSet instead.\n\tRecursionBlockTreads int `json:\"recursionBlockTreads,omitempty\"`\n}\n\nconst defaultAutoResolveConflicts = true\n\nvar defaultRouting = Routing{ //nolint:gochecknoglobals // constant\n\tVirtualSubnet:        defaultVirtualSubnet,\n\tAutoResolveConflicts: defaultAutoResolveConflicts,\n}\n\nfunc (r *Routing) defaults() DefaultsAware {\n\treturn &defaultRouting\n}\n\nfunc (r *Routing) merge(o *Routing) {\n\tif len(o.AlsoProxy) > 0 {\n\t\tr.AlsoProxy = o.AlsoProxy\n\t} else if len(o.OldAlsoProxy) > 0 {\n\t\tr.AlsoProxy = o.OldAlsoProxy\n\t}\n\tif len(o.NeverProxy) > 0 {\n\t\tr.NeverProxy = o.NeverProxy\n\t} else if len(o.OldNeverProxy) > 0 {\n\t\tr.NeverProxy = o.OldNeverProxy\n\t}\n\tif len(o.AllowConflicting) > 0 {\n\t\tr.AllowConflicting = o.AllowConflicting\n\t} else if len(o.OldAllowConflicting) > 0 {\n\t\tr.AllowConflicting = o.OldAllowConflicting\n\t}\n\tif len(o.Subnets) > 0 {\n\t\tr.Subnets = o.Subnets\n\t}\n\tif o.VirtualSubnet != defaultVirtualSubnet {\n\t\tr.VirtualSubnet = o.VirtualSubnet\n\t}\n\tif o.AutoResolveConflicts != defaultAutoResolveConflicts { //nolint:staticcheck // keep for the semantic clarity\n\t\tr.AutoResolveConflicts = o.AutoResolveConflicts\n\t}\n\tif o.UseTAP {\n\t\tr.UseTAP = o.UseTAP\n\t}\n}\n\n// IsZero controls whether this element will be included in marshalled output.\nfunc (r *Routing) IsZero() bool {\n\treturn r == nil || isDefault(r)\n}\n\nfunc (r *Routing) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(r))\n}\n\nfunc (r *Routing) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype routing Routing\n\twp := (*routing)(r)\n\treturn json.UnmarshalDecode(in, &wp)\n}\n\nfunc (d *DNS) Equal(o *DNS) bool {\n\tif d == nil || o == nil {\n\t\treturn d == o\n\t}\n\treturn slices.Equal(o.LocalAddresses, d.LocalAddresses) &&\n\t\to.VIFAddress == d.VIFAddress &&\n\t\to.LookupTimeout == d.LookupTimeout &&\n\t\to.RecursionCheck == d.RecursionCheck &&\n\t\tslices.Equal(o.IncludeSuffixes, d.IncludeSuffixes) &&\n\t\tslices.Equal(o.ExcludeSuffixes, d.ExcludeSuffixes) &&\n\t\tslices.Equal(o.Excludes, d.Excludes) &&\n\t\tslices.Equal(o.Mappings, d.Mappings)\n}\n\nvar DefaultExcludeSuffixes = []string{ //nolint:gochecknoglobals // constant\n\t\".com\",\n\t\".io\",\n\t\".net\",\n\t\".org\",\n\t\".ru\",\n}\n\nvar defaultDNS = DNS{ //nolint:gochecknoglobals // constant\n\tExcludeSuffixes: DefaultExcludeSuffixes,\n}\n\nfunc (d *DNS) defaults() DefaultsAware {\n\treturn &defaultDNS\n}\n\n// merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (d *DNS) merge(o *DNS) {\n\tmergeNonDefaults(d, o)\n}\n\n// IsZero controls whether this element will be included in marshalled output.\nfunc (d *DNS) IsZero() bool {\n\treturn d == nil || d.Equal(&defaultDNS)\n}\n\nfunc (d *DNS) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mapWithoutDefaults(d))\n}\n\nfunc (d *DNS) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\t// Prevent that the original object is cleared when an empty object is decoded by passing the address\n\t// of the pointer to the object. The unmarshal will then instead clear the pointer (wp becomes nil) and\n\t// leave the underlying object intact. In other words, this code achieves \"omitempty\" during unmarshal.\n\ttype dns DNS\n\twp := (*dns)(d)\n\terr := json.UnmarshalDecode(in, &wp)\n\tif err == nil {\n\t\tif d.LocalIP.IsValid() && len(d.LocalAddresses) == 0 {\n\t\t\td.LocalAddresses = []netip.AddrPort{netip.AddrPortFrom(d.LocalIP, 53)}\n\t\t}\n\t\tif d.LocalAddress.IsValid() && len(d.LocalAddresses) == 0 {\n\t\t\td.LocalAddresses = []netip.AddrPort{d.LocalAddress}\n\t\t}\n\t\tif d.RemoteIP.IsValid() && !d.VIFAddress.IsValid() {\n\t\t\td.VIFAddress = netip.AddrPortFrom(d.RemoteIP, 53)\n\t\t}\n\t}\n\treturn err\n}\n\ntype configKey struct{}\n\n// WithConfig returns a context with the given Config.\nfunc WithConfig(ctx context.Context, cfg Config) context.Context {\n\tcfgCopy := *cfg.Base()\n\treturn context.WithValue(ctx, configKey{}, &cfgCopy)\n}\n\nfunc GetConfig(ctx context.Context) Config {\n\tif cfg, ok := ctx.Value(configKey{}).(*config); ok {\n\t\treturn cfg\n\t}\n\tpanic(\"no Config has been set\")\n}\n\n// ReplaceConfig replaces the config last stored using WithConfig with the given Config.\n// The function returns true if an existing config was replaced, false if no such config\n// existed.\nfunc ReplaceConfig(ctx context.Context, cfg Config) bool {\n\tif cfgImpl, ok := ctx.Value(configKey{}).(*config); ok {\n\t\t*cfgImpl = *cfg.Base()\n\t\treturn true\n\t}\n\treturn false\n}\n\ntype configFileKey struct{}\n\n// WithConfigFile sets the config file to use and overrides the default which is using \"config.yml\" from the AppUserConfigDir.\nfunc WithConfigFile(ctx context.Context, configFile string) context.Context {\n\treturn context.WithValue(ctx, configFileKey{}, configFile)\n}\n\n// GetConfigFile gets the path to the configFile.\nfunc GetConfigFile(c context.Context) string {\n\tif configFile, ok := c.Value(configFileKey{}).(string); ok {\n\t\treturn configFile\n\t}\n\treturn filepath.Join(filelocation.AppUserConfigDir(c), ConfigFile)\n}\n\n// GetDefaultConfig returns the default configuration settings.\nfunc GetDefaultConfig() Config {\n\treturn getDefaultConfig()\n}\n\nvar defaultConfig = config{ //nolint:gochecknoglobals // constant\n\tOSSpecificConfig: GetDefaultOSSpecificConfig(),\n\tTimeoutsV:        defaultTimeouts,\n\tLogLevelsV:       defaultLogLevels,\n\tImagesV:          defaultImages,\n\tGrpcV:            defaultGrpc,\n\tInterceptV:       defaultIntercept,\n\tClusterV:         defaultCluster,\n\tDockerV:          defaultDocker,\n\tDNSV:             defaultDNS,\n\tRoutingV:         defaultRouting,\n\tHelmV:            defaultHelm,\n}\n\n// getDefaultConfig returns the default configuration settings.\nfunc getDefaultConfig() *config {\n\tc := new(config)\n\t*c = defaultConfig\n\treturn c\n}\n\n// LoadConfig loads and returns the Telepresence configuration as stored in filelocation.AppUserConfigDir\n// or filelocation.AppSystemConfigDirs.\nfunc LoadConfig(c context.Context) (cfg Config, err error) {\n\tfileName := GetConfigFile(c)\n\tcfg = GetDefaultConfig()\n\tbs, err := os.ReadFile(fileName)\n\tif err != nil {\n\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn cfg, nil\n\t\t}\n\t\treturn nil, errcat.Config.New(err)\n\t}\n\tfileConfig, err := ParseConfigYAML(c, fileName, bs)\n\tif err != nil {\n\t\treturn nil, errcat.Config.New(err)\n\t}\n\tcfg.DestructiveMerge(fileConfig)\n\treturn cfg, nil\n}\n\n// RoutingSnake is the same as Routing but with snake_case json/yaml names.\ntype RoutingSnake struct {\n\tSubnets              []netip.Prefix `json:\"subnets\"`\n\tAlsoProxy            []netip.Prefix `json:\"also_proxy_subnets\"`\n\tNeverProxy           []netip.Prefix `json:\"never_proxy_subnets\"`\n\tAllowConflicting     []netip.Prefix `json:\"allow_conflicting_subnets\"`\n\tVirtualSubnet        netip.Prefix   `json:\"virtual_subnet\"`\n\tAutoResolveConflicts bool           `json:\"auto_resolve_conflicts\"`\n\tUseTAP               bool           `json:\"use_tap\"`\n}\n\n// DNSMapping contains a hostname and its associated alias. When requesting the name, the intended behavior is\n// to resolve the alias instead.\ntype DNSMapping struct {\n\tName     string `json:\"name,omitempty\" yaml:\"name,omitempty\"`\n\tAliasFor string `json:\"aliasFor,omitempty\" yaml:\"aliasFor,omitempty\"`\n}\n\ntype DNSMappings []*DNSMapping\n\nfunc (d *DNSMappings) FromRPC(rpcMappings []*daemon.DNSMapping) {\n\t*d = make(DNSMappings, 0, len(rpcMappings))\n\tfor i := range rpcMappings {\n\t\t*d = append(*d, &DNSMapping{\n\t\t\tName:     rpcMappings[i].Name,\n\t\t\tAliasFor: rpcMappings[i].AliasFor,\n\t\t})\n\t}\n}\n\nfunc (d DNSMappings) ToRPC() []*daemon.DNSMapping {\n\trpcMappings := make([]*daemon.DNSMapping, 0, len(d))\n\tfor i := range d {\n\t\trpcMappings = append(rpcMappings, &daemon.DNSMapping{\n\t\t\tName:     d[i].Name,\n\t\t\tAliasFor: d[i].AliasFor,\n\t\t})\n\t}\n\treturn rpcMappings\n}\n\ntype DNS struct {\n\tError string `json:\"error\"`\n\n\t// LocalIP\n\t//\n\t// Deprecated: Use LocalAddresses.\n\tLocalIP netip.Addr `json:\"localIP\"`\n\n\t// RemoteIP\n\t//\n\t// Deprecated: Use VIFAddress.\n\tRemoteIP netip.Addr `json:\"remoteIP\"`\n\n\t// Deprecated: Use LocalAddresses.\n\tLocalAddress netip.AddrPort `json:\"localAddress\"`\n\n\tLocalAddresses   []netip.AddrPort `json:\"localAddresses\"`\n\tVIFAddress       netip.AddrPort   `json:\"vifAddress\"`\n\tIncludeSuffixes  []string         `json:\"includeSuffixes\"`\n\tExcludeSuffixes  []string         `json:\"excludeSuffixes\"`\n\tExcludes         []string         `json:\"excludes\"`\n\tMappings         DNSMappings      `json:\"mappings\"`\n\tLookupTimeout    time.Duration    `json:\"lookupTimeout,format:units\"`\n\tRecursionCheck   bool             `json:\"recursionCheck\"`\n\tUseComplexLookup bool             `json:\"useComplexLookup\"`\n}\n\n// DNSSnake is the same as DNS but with snake_case json/yaml names.\ntype DNSSnake struct {\n\tError            string           `json:\"error\"`\n\tLocalAddresses   []netip.AddrPort `json:\"local_addresses\"`\n\tVIFAddress       netip.AddrPort   `json:\"vif_address\"`\n\tIncludeSuffixes  []string         `json:\"include_suffixes\"`\n\tExcludeSuffixes  []string         `json:\"exclude_suffixes\"`\n\tExcludes         []string         `json:\"excludes\"`\n\tMappings         DNSMappings      `json:\"mappings\"`\n\tLookupTimeout    time.Duration    `json:\"lookup_timeout,format:units\"`\n\tRecursionCheck   bool             `json:\"recursion_check\"`\n\tUseComplexLookup bool             `json:\"use_complex_lookup\"`\n}\n\nfunc (d *DNS) ToRPC() *daemon.DNSConfig {\n\trd := daemon.DNSConfig{\n\t\tExcludeSuffixes: d.ExcludeSuffixes,\n\t\tIncludeSuffixes: d.IncludeSuffixes,\n\t\tExcludes:        d.Excludes,\n\t\tLookupTimeout:   durationpb.New(d.LookupTimeout),\n\t\tRecursionCheck:  d.RecursionCheck,\n\t\tError:           d.Error,\n\t}\n\tfor _, a := range d.LocalAddresses {\n\t\taBin, _ := a.MarshalBinary()\n\t\trd.LocalAddresses = append(rd.LocalAddresses, aBin)\n\t}\n\tif d.VIFAddress.IsValid() {\n\t\trd.VifAddress, _ = d.VIFAddress.MarshalBinary()\n\t}\n\tif len(d.Mappings) > 0 {\n\t\trd.Mappings = make([]*daemon.DNSMapping, len(d.Mappings))\n\t\tfor i, n := range d.Mappings {\n\t\t\trd.Mappings[i] = &daemon.DNSMapping{\n\t\t\t\tName:     n.Name,\n\t\t\t\tAliasFor: n.AliasFor,\n\t\t\t}\n\t\t}\n\t}\n\treturn &rd\n}\n\nfunc (d *DNS) ToSnake() *DNSSnake {\n\treturn &DNSSnake{\n\t\tLocalAddresses:   d.LocalAddresses,\n\t\tVIFAddress:       d.VIFAddress,\n\t\tExcludeSuffixes:  d.ExcludeSuffixes,\n\t\tIncludeSuffixes:  d.IncludeSuffixes,\n\t\tExcludes:         d.Excludes,\n\t\tMappings:         d.Mappings,\n\t\tLookupTimeout:    d.LookupTimeout,\n\t\tRecursionCheck:   d.RecursionCheck,\n\t\tUseComplexLookup: d.UseComplexLookup,\n\t\tError:            d.Error,\n\t}\n}\n\nfunc MappingsFromRPC(mappings []*daemon.DNSMapping) DNSMappings {\n\tif l := len(mappings); l > 0 {\n\t\tml := make(DNSMappings, l)\n\t\tfor i, m := range mappings {\n\t\t\tml[i] = &DNSMapping{\n\t\t\t\tName:     m.Name,\n\t\t\t\tAliasFor: m.AliasFor,\n\t\t\t}\n\t\t}\n\t\treturn ml\n\t}\n\treturn nil\n}\n\nfunc (r *Routing) ToSnake() *RoutingSnake {\n\treturn &RoutingSnake{\n\t\tSubnets:              r.Subnets,\n\t\tAlsoProxy:            r.AlsoProxy,\n\t\tNeverProxy:           r.NeverProxy,\n\t\tAllowConflicting:     r.AllowConflicting,\n\t\tAutoResolveConflicts: r.AutoResolveConflicts,\n\t\tVirtualSubnet:        r.VirtualSubnet,\n\t\tUseTAP:               r.UseTAP,\n\t}\n}\n\ntype SessionConfig struct {\n\tConfig       `json:\"clientConfig\"`\n\tClientFile   string `json:\"clientFile\"`\n\tLogDirectory string `json:\"logDirectory\"`\n}\n\nfunc (sc *SessionConfig) UnmarshalJSON(data []byte) error {\n\ttype tmpType SessionConfig\n\tvar tmpJSON tmpType\n\ttmpJSON.Config = GetDefaultConfig()\n\tif err := json2.Unmarshal(data, &tmpJSON, false); err != nil {\n\t\treturn err\n\t}\n\t*sc = SessionConfig(tmpJSON)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/config_darwin.go",
    "content": "package client\n\nconst defaultAddHostGateway = false\n"
  },
  {
    "path": "pkg/client/config_linux.go",
    "content": "package client\n\nconst defaultAddHostGateway = true\n"
  },
  {
    "path": "pkg/client/config_test.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"k8s.io/apimachinery/pkg/api/resource\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc TestGetConfig(t *testing.T) {\n\tconfig := `\ntimeouts:\n  clusterConnect: 25s\n  proxyDial: 17s\nlogLevels:\n  rootDaemon: trace\ndns:\n  recursionCheck: true\nimages:\n  registry: testregistry.io\n  agentImage: ambassador-telepresence-agent-image:0.0.2\n  clientImage: ambassador-telepresence-image:0.0.2\nintercept:\n  defaultPort: 9080\n  useFtp: true\nrouting:\n  virtualSubnet: 192.169.0.0/16\n`\n\n\ttmp := t.TempDir()\n\tuser := filepath.Join(tmp, \"user\")\n\trequire.NoError(t, os.MkdirAll(user, 0o700))\n\trequire.NoError(t, os.WriteFile(filepath.Join(user, ConfigFile), []byte(config), 0o600))\n\n\tc := testutil.NewContext(t, false)\n\tc = filelocation.WithAppUserConfigDir(c, user)\n\tenv, err := LoadEnv()\n\trequire.NoError(t, err)\n\tc = WithEnv(c, &env)\n\n\tcfg, err := LoadConfig(c)\n\trequire.NoError(t, err)\n\tc = WithConfig(c, cfg)\n\n\tcfg = GetConfig(c)\n\tto := cfg.Timeouts()\n\tassert.Equal(t, 25*time.Second, to.PrivateClusterConnect)    // from user\n\tassert.Equal(t, 17*time.Second, to.PrivateProxyDial)         // from user\n\tassert.Equal(t, clog.LevelTrace, cfg.LogLevels().RootDaemon) // from user\n\n\tassert.Equal(t, \"testregistry.io\", cfg.Images().PrivateRegistry)                             // from user\n\tassert.Equal(t, \"ambassador-telepresence-agent-image:0.0.2\", cfg.Images().PrivateAgentImage) // from user\n\tassert.Equal(t, \"ambassador-telepresence-image:0.0.2\", cfg.Images().PrivateClientImage)      // from user\n\tassert.Equal(t, 9080, cfg.Intercept().DefaultPort)                                           // from user\n\tassert.True(t, cfg.Intercept().UseFtp)                                                       // from user\n\tassert.True(t, cfg.DNS().RecursionCheck)                                                     // from user\n\tassert.Equal(t, cfg.Routing().VirtualSubnet, netip.MustParsePrefix(\"192.169.0.0/16\"))        // from user\n}\n\nfunc Test_ConfigMarshalYAML(t *testing.T) {\n\tctx := testutil.NewContext(t, true)\n\tenv, err := LoadEnv()\n\trequire.NoError(t, err)\n\tctx = WithEnv(ctx, &env)\n\tcfg := GetDefaultConfig()\n\tcfg.Images().PrivateAgentImage = \"something:else\"\n\tcfg.Timeouts().PrivateTrafficManagerAPI = defaultTimeoutsTrafficManagerAPI + 20*time.Second\n\tcfg.LogLevels().UserDaemon = clog.LevelTrace\n\tcfg.Grpc().MaxReceiveSizeV, _ = resource.ParseQuantity(\"20Mi\")\n\tcfg.Intercept().DefaultPort = 9080\n\tcfg.Cluster().DefaultManagerNamespace = \"hello-there\"\n\tcfgBytes, err := cfg.MarshalYAML()\n\trequire.NoError(t, err)\n\n\t// Store YAML in file\n\ttmp := t.TempDir()\n\trequire.NoError(t, os.WriteFile(filepath.Join(tmp, ConfigFile), cfgBytes, 0o600))\n\tctx = filelocation.WithAppUserConfigDir(ctx, tmp)\n\n\t// Load from file and compare\n\tcfg2, err := LoadConfig(ctx)\n\trequire.NoError(t, err)\n\trequire.Equal(t, cfg, cfg2)\n}\n\nfunc Test_ConfigMarshalYAMLDefaults(t *testing.T) {\n\tcfgBytes, err := GetDefaultConfig().MarshalYAML()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"{}\\n\", string(cfgBytes))\n}\n\nfunc Test_ConfigUnmarshalYAMLEmpty(t *testing.T) {\n\tcfg, err := ParseConfigYAML(testutil.NewContext(t, true), \"\", []byte(\"{}\"))\n\trequire.NoError(t, err)\n\trequire.Equal(t, GetDefaultConfig(), cfg)\n}\n\nfunc Test_ConfigUnmarshalYAMLBooleanTrueDefault(t *testing.T) {\n\tcfg, err := ParseConfigYAML(testutil.NewContext(t, true), \"\", []byte(`\ndocker:\n  enableIPv4: true\n`))\n\trequire.NoError(t, err)\n\trequire.Equal(t, GetDefaultConfig(), cfg)\n}\n\nfunc Test_ConfigUnmarshalYAMLBooleanTrueDefaultFalse(t *testing.T) {\n\tcfg, err := ParseConfigYAML(testutil.NewContext(t, true), \"\", []byte(`\ndocker:\n  enableIPv4: false\n`))\n\trequire.NoError(t, err)\n\trequire.NotEqual(t, GetDefaultConfig(), cfg)\n}\n\nfunc Test_ConfigUnmarshalYAMLEmptyParent(t *testing.T) {\n\tcfg, err := ParseConfigYAML(testutil.NewContext(t, true), \"\", []byte(`\ndocker:\n`))\n\trequire.NoError(t, err)\n\trequire.Equal(t, GetDefaultConfig(), cfg)\n}\n\nfunc Test_ConfigMarshalYAMLDefaultsNotEmitted(t *testing.T) {\n\tcfg := GetDefaultConfig()\n\tlls := cfg.LogLevels()\n\tlls.UserDaemon = defaultLogLevels.UserDaemon\n\tlls.RootDaemon = slog.LevelDebug\n\tcfgBytes, err := cfg.MarshalYAML()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"logLevels:\\n  rootDaemon: DEBUG\\n\", string(cfgBytes))\n}\n\nfunc Test_ConfigUnmarshalUnsupported(t *testing.T) {\n\tconfig := []byte(`---\nlogLevels:\n  userDaemon: debug\n  fooDaemon: debug\n`)\n\tcfg, err := ParseConfigYAML(context.Background(), \"config.yml\", config)\n\trequire.NoError(t, err)\n\trequire.Equal(t, cfg.LogLevels().UserDaemon, slog.LevelDebug)\n}\n"
  },
  {
    "path": "pkg/client/config_unix.go",
    "content": "//go:build !windows\n\npackage client\n\nimport \"net/netip\"\n\n// defaultVirtualSubnet A randomly chosen class E subnet.\nvar defaultVirtualSubnet = netip.MustParsePrefix(\"246.246.0.0/16\") //nolint:gochecknoglobals // constant\n\ntype OSSpecificConfig struct{}\n\nfunc GetDefaultOSSpecificConfig() OSSpecificConfig {\n\treturn OSSpecificConfig{}\n}\n\nfunc (c *OSSpecificConfig) Merge(o *OSSpecificConfig) {\n}\n"
  },
  {
    "path": "pkg/client/config_util.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\n// ReloadLogLevel calls SetLevel with the log level defined for the current process.\n// Assumes that the config has already been reloaded.\nfunc ReloadLogLevel(c context.Context) {\n\tnewCfg := GetConfig(c)\n\tvar level slog.Level\n\tlevels := newCfg.LogLevels()\n\tswitch ProcessName() {\n\tcase RootDaemonName:\n\t\tlevel = levels.RootDaemon\n\tcase UserDaemonName:\n\t\tlevel = levels.UserDaemon\n\tcase KubeAuthDaemonName:\n\t\tlevel = levels.KubeAuthDaemon\n\tdefault:\n\t\tlevel = levels.CLI\n\t}\n\tif clog.SetTreeLevel(c, level) {\n\t\tclog.Infof(c, \"Logging at this level %q\", clog.LevelWithTrace(level))\n\t}\n}\n"
  },
  {
    "path": "pkg/client/config_windows.go",
    "content": "package client\n\nimport \"net/netip\"\n\nconst defaultAddHostGateway = false\n\ntype OSSpecificConfig struct {\n\tNetwork Network `json:\"network,omitzero\"`\n}\n\nfunc GetDefaultOSSpecificConfig() OSSpecificConfig {\n\treturn OSSpecificConfig{\n\t\tNetwork: Network{\n\t\t\tDNSWithFallback: defaultDNSWithFallback,\n\t\t},\n\t}\n}\n\n// Merge merges this instance with the non-zero values of the given argument. The argument values take priority.\nfunc (c *OSSpecificConfig) Merge(o *OSSpecificConfig) {\n\tc.Network.merge(&o.Network)\n}\n\ntype GSCStrategy string\n\nconst (\n\tdefaultDNSWithFallback = true\n)\n\n// defaultVirtualSubnet is an IP that, on windows, is built from 16 class C subnets which were chosen randomly,\n// hoping that they don't collide with another subnet.\nvar defaultVirtualSubnet = netip.MustParsePrefix(\"211.55.48.0/20\") //nolint:gochecknoglobals // constant\n\ntype Network struct {\n\tDNSWithFallback bool `json:\"dnsWithFallback,omitempty\"`\n}\n\nfunc (n *Network) merge(o *Network) {\n\tif o.DNSWithFallback != defaultDNSWithFallback { //nolint:staticcheck // keep for the semantic clarity\n\t\tn.DNSWithFallback = o.DNSWithFallback\n\t}\n}\n\nfunc (n *Network) IsZero() bool {\n\treturn n == nil || n.DNSWithFallback == defaultDNSWithFallback //nolint:staticcheck // keep for the semantic clarity\n}\n"
  },
  {
    "path": "pkg/client/const.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\nconst (\n\t// APIVersion is the API version of the daemon and connector API.\n\tAPIVersion         = 3\n\tUserDaemonName     = \"userd\"\n\tRootDaemonName     = \"rootd\"\n\tKubeAuthDaemonName = \"kubeauthd\"\n)\n\n// DisplayVersion returns a printable version for `telepresence`.\nfunc DisplayVersion() string {\n\treturn fmt.Sprintf(\"%s (api v%d)\", Version(), APIVersion)\n}\n\n// GetExe returns the name of the running executable.\nfunc GetExe(ctx context.Context) string {\n\t// Figure out our executable\n\texeName, err := dos.Executable(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exeName\n}\n\nfunc isDaemonName(name string) bool {\n\tswitch name {\n\tcase UserDaemonName, RootDaemonName, KubeAuthDaemonName:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc IsDaemon() bool {\n\treturn isDaemonName(ProcessName())\n}\n\nvar ProcessName = func() string { //nolint:gochecknoglobals // extension point\n\ta := os.Args\n\tvar pn string\n\tswitch {\n\tcase len(a) > 2 && a[1] == \"help\":\n\t\tpn = a[2]\n\tcase len(a) > 1:\n\t\tpn = a[1]\n\tdefault:\n\t\tpn = filepath.Base(a[0])\n\t\tif runtime.GOOS == \"windows\" {\n\t\t\tpn = strings.TrimSuffix(pn, \".exe\")\n\t\t}\n\t}\n\treturn pn\n}\n\nconst Tel2SubDomain = \"tel2-search\"\n"
  },
  {
    "path": "pkg/client/docker/container.go",
    "content": "package docker\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"os/exec\"\n\t\"slices\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/docker/docker/api/types/container\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/flags\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nconst Exe = \"docker\"\n\nfunc Start(ctx context.Context, daemonInContainer bool, args ...string) (cni *ContainerInfo, cc *exec.Cmd, err error) {\n\tdockerOpts := []string{\"create\"}\n\tvar nwName string\n\tif daemonInContainer {\n\t\tvar dns netip.Addr\n\t\tdns, nwName, err = GetDaemonContainerNetworkInfo(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\tdockerOpts = append(dockerOpts, \"--dns\", dns.String())\n\t}\n\n\tcc = proc.StdCommand(ctx, Exe, slices.Insert(args, 0, dockerOpts...)...)\n\tidReader := &bytes.Buffer{}\n\tcc.Stdout = idReader\n\tcc.Env = dos.Environ(ctx)\n\terr = cc.Run()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcontainerID := strings.TrimSpace(idReader.String())\n\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tif nwName != \"\" {\n\t\tif err = cli.NetworkConnect(ctx, nwName, containerID, nil); err != nil {\n\t\t\tif !strings.Contains(err.Error(), \"already exists\") {\n\t\t\t\tclog.Debugf(ctx, \"failed to connect network %s to container %s: %v\", nwName, containerID, err)\n\t\t\t}\n\t\t}\n\t}\n\n\tstartArgs := []string{\"start\", \"--attach\"}\n\tif flags.HasOption(\"interactive\", 'i', args) {\n\t\tstartArgs = append(startArgs, \"--interactive\")\n\t}\n\tstartArgs = append(startArgs, containerID)\n\tcc = proc.StdCommand(ctx, Exe, startArgs...)\n\tcc.Stdin = dos.Stdin(ctx)\n\tcc.Env = dos.Environ(ctx)\n\terr = cc.Start()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tcni, err = GetContainerInfo(ctx, containerID, nwName)\n\tif err != nil {\n\t\tif errdefs.IsNotFound(err) {\n\t\t\t// Container is already done, so not much left to do here.\n\t\t\terr = cc.Wait()\n\t\t}\n\t\treturn nil, nil, errcat.NoDaemonLogs.New(err)\n\t}\n\treturn cni, cc, nil\n}\n\nfunc StopContainer(ctx context.Context, nameOrID string) error {\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\topts := container.StopOptions{}\n\ttimeout := client.GetConfig(ctx).Timeouts().Get(client.TimeoutContainerShutdown)\n\tif timeout > 0 {\n\t\tsecs := int(timeout / time.Second)\n\t\topts.Timeout = &secs\n\t\tclog.Debugf(ctx, \"Stopping container %s with a grace period of %d seconds\", nameOrID, secs)\n\t} else {\n\t\tclog.Debugf(ctx, \"Stopping container %s with default grace period\", nameOrID)\n\t}\n\t_, err = cli.ContainerInspect(ctx, nameOrID)\n\tif err != nil {\n\t\tif errdefs.IsNotFound(err) {\n\t\t\terr = nil\n\t\t} else {\n\t\t\tclog.Errorf(ctx, \"Failed to inspect container %s: %v\", nameOrID, err)\n\t\t}\n\t\treturn err\n\t}\n\terr = cli.ContainerStop(ctx, nameOrID, opts)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to stop container %s: %v\", nameOrID, err)\n\t\tclog.Error(ctx, err)\n\t\treturn err\n\t}\n\tclog.Debugf(ctx, \"Container %s stopped\", nameOrID)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/docker/context.go",
    "content": "package docker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/docker/docker/client\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\ntype clientKey struct{}\n\ntype clientHandle struct {\n\tsync.Mutex\n\tcli *client.Client\n}\n\nfunc (h *clientHandle) getClient(ctx context.Context) (*client.Client, error) {\n\th.Lock()\n\tdefer h.Unlock()\n\tif h.cli == nil {\n\t\tcmd := proc.CommandContext(ctx, Exe, \"context\", \"inspect\", \"--format\", \"{{.Endpoints.docker.Host}}\")\n\t\tstdout, err := proc.CaptureErr(cmd)\n\t\topts := []client.Opt{client.FromEnv, client.WithAPIVersionNegotiation()}\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to retrieve docker context: %v\", err)\n\t\t}\n\t\tif host := strings.TrimSpace(string(stdout)); host != \"\" {\n\t\t\topts = append(opts, client.WithHost(host))\n\t\t}\n\t\tcli, err := client.NewClientWithOpts(opts...)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\th.cli = cli\n\t}\n\treturn h.cli, nil\n}\n\nfunc EnableClient(ctx context.Context) context.Context {\n\tif ctx.Value(clientKey{}) == nil {\n\t\tctx = context.WithValue(ctx, clientKey{}, &clientHandle{})\n\t}\n\treturn ctx\n}\n\nfunc GetClient(ctx context.Context) (*client.Client, error) {\n\tif h, ok := ctx.Value(clientKey{}).(*clientHandle); ok {\n\t\treturn h.getClient(ctx)\n\t}\n\tpanic(\"docker client not initialized\")\n}\n"
  },
  {
    "path": "pkg/client/docker/daemon.go",
    "content": "// Package docker contains the functions necessary to start or discover a Telepresence daemon running in a docker container.\npackage docker\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"net\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/docker/docker/api/types/container\"\n\t\"github.com/docker/docker/api/types/filters\"\n\t\"github.com/docker/docker/api/types/network\"\n\tdockerClient \"github.com/docker/docker/client\"\n\t\"github.com/go-json-experiment/json\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/authenticator/patcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/global\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker/kubeauth\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/routing\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nconst (\n\ttelepresenceImage = \"telepresence\"\n\tTpCache           = \"/root/.cache/telepresence\"\n\tDockerTpConfig    = \"/root/.config/telepresence\"\n\tDockerTpLog       = \"/root/.cache/telepresence/logs\"\n)\n\nvar ClientImageName = telepresenceImage //nolint:gochecknoglobals // extension point\n\n// ClientImage returns the fully qualified name of the docker image that corresponds to\n// the version of the current executable.\nfunc ClientImage(ctx context.Context) string {\n\timages := client.GetConfig(ctx).Images()\n\timg := images.ClientImage(ctx)\n\tif img == \"\" {\n\t\tregistry := images.Registry(ctx)\n\t\timg = registry + \"/\" + ClientImageName + \":\" + strings.TrimPrefix(version.Version, \"v\")\n\t}\n\treturn img\n}\n\n// DaemonOptions returns the options necessary to pass to a docker run when starting a daemon container.\nfunc DaemonOptions(ctx context.Context, daemonID *daemon.Identifier, daemonPortOnHost uint16) (opts []string, err error) {\n\tipv6, err := UseIPv6(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tap := daemonAddr(ipv6)\n\topts = []string{\n\t\t\"--name\", daemonID.ContainerName(),\n\t\t\"--cap-add\", \"NET_ADMIN\",\n\t\t\"--device\", \"/dev/net/tun:/dev/net/tun\",\n\t\t\"--pid\", \"host\",\n\t\t\"-e\", fmt.Sprintf(\"TELEPRESENCE_UID=%d\", os.Getuid()),\n\t\t\"-e\", fmt.Sprintf(\"TELEPRESENCE_GID=%d\", os.Getgid()),\n\t\t\"-p\", fmt.Sprintf(\"%s:%d/tcp\", netip.AddrPortFrom(ap, daemonPortOnHost), client.GetConfig(ctx).Grpc().DaemonPort),\n\t\t\"-v\", fmt.Sprintf(\"%s:%s:ro\", filepath.Dir(client.GetConfigFile(ctx)), DockerTpConfig),\n\t\t\"-v\", fmt.Sprintf(\"%s:%s\", filelocation.AppUserCacheDir(ctx), TpCache),\n\t\t\"-v\", fmt.Sprintf(\"%s:%s\", filelocation.AppUserLogDir(ctx), DockerTpLog),\n\t}\n\tcr := daemon.GetRequest(ctx)\n\tfor _, ep := range cr.ExposedPorts {\n\t\topts = append(opts, \"-p\", ep)\n\t}\n\tif cr.Hostname != \"\" {\n\t\topts = append(opts, \"--hostname\", cr.Hostname)\n\t}\n\topts, err = appendOSSpecificContainerOpts(ctx, opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif ipv6 {\n\t\topts = append(opts,\n\t\t\t\"--sysctl\", \"net.ipv6.conf.all.forwarding=1\",\n\t\t\t\"--sysctl\", \"net.ipv6.conf.all.disable_ipv6=0\",\n\t\t)\n\t}\n\tcfg := client.GetConfig(ctx).Docker()\n\tif cfg.HostGateway != \"\" && (cfg.AddHostGateway || cfg.HostGateway != client.DefaultHostGateway) {\n\t\topts = append(opts, \"--add-host\", cfg.HostGateway+\":host-gateway\")\n\t}\n\treturn opts, nil\n}\n\n// DaemonArgs returns the arguments to pass to a docker run when starting a container daemon.\nfunc DaemonArgs(ctx context.Context, daemonID *daemon.Identifier) []string {\n\tgrpcCfg := client.GetConfig(ctx).Grpc()\n\treturn []string{\n\t\tclient.UserDaemonName,\n\t\t\"--config\", filepath.Join(DockerTpConfig, filepath.Base(client.GetConfigFile(ctx))),\n\t\t\"--name\", \"docker-\" + daemonID.String(),\n\t\t\"--address\", fmt.Sprintf(\":%d\", grpcCfg.DaemonPort),\n\t\t\"--embed-network\",\n\t\t\"--teleroute-port\", strconv.Itoa(int(grpcCfg.TeleroutePort)),\n\t}\n}\n\n// ConnectDaemon connects to a containerized daemon at the given address.\nfunc ConnectDaemon(ctx context.Context, info *daemon.Info) (conn *grpc.ClientConn, err error) {\n\tipv6, err := UseIPv6(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn grpc.NewClient(\n\t\tnetip.AddrPortFrom(daemonAddr(ipv6), info.DaemonPort).String(),\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithNoProxy())\n}\n\nfunc daemonAddr(ipv6 bool) netip.Addr {\n\tif ipv6 {\n\t\treturn netip.IPv6Loopback()\n\t}\n\treturn netip.AddrFrom4([4]byte{127, 0, 0, 1})\n}\n\nconst (\n\tkubeAuthPortFile = client.KubeAuthDaemonName + \".port\"\n)\n\ntype ContainerInfo struct {\n\tID   string\n\tName string\n\tPid  int\n\tIPv4 netip.Addr\n\tIPv6 netip.Addr\n}\n\n// GetDaemonContainerNetworkInfo checks if the daemon VIF routes any subnets. If it does, then the DNS IP\n// assigned to the VIF and the network name of the teleroute network is returned. Otherwise, the method\n// returns the daemon's IP on the default bridge as the DNS address and an empty string as the network name.\nfunc GetDaemonContainerNetworkInfo(ctx context.Context) (dns netip.Addr, networkName string, err error) {\n\tud := daemon.MustGetUserClient(ctx)\n\tinfo := ud.DaemonInfo()\n\tstatus, err := ud.Status(ctx, &empty.Empty{})\n\tif err != nil {\n\t\treturn dns, \"\", tpGrpc.FromGRPC(err)\n\t}\n\n\trootCfg, err := daemon.GetRootClientConfig(status.DaemonStatus)\n\tif err != nil {\n\t\treturn dns, \"\", err\n\t}\n\n\tif len(rootCfg.Routing().Subnets) > 0 {\n\t\txi, err := GetContainerInfo(ctx, info.ContainerID, info.Name)\n\t\tif err == nil {\n\t\t\tif xi.IPv4.IsValid() {\n\t\t\t\tdns = xi.IPv4\n\t\t\t} else {\n\t\t\t\tdns = xi.IPv6\n\t\t\t}\n\t\t} else {\n\t\t\tdns = rootCfg.DNS().VIFAddress.Addr()\n\t\t}\n\t\tnetworkName = info.Name\n\t} else {\n\t\t// The daemon doesn't route any subnets because it found that the container already had access\n\t\t// to the cluster resources. It's then assumed that other containers will have that too.\n\t\t// This means that:\n\t\t//\n\t\t//   1. The IP of the daemon container is the one assigned to the default bridge network.\n\t\t//   2. The IP of the daemon container can act as the DNS IP.\n\t\tdns = info.ContainerIP\n\t}\n\treturn dns, networkName, nil\n}\n\n// GetContainerInfo returns the name and process ID of the container with the given ID along with its associated IP in the given network.\nfunc GetContainerInfo(ctx context.Context, cid string, network string) (*ContainerInfo, error) {\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdcfg := client.GetConfig(ctx).Docker()\n\n\tbo := backoff.NewExponentialBackOff()\n\tbo.MaxInterval = 300 * time.Millisecond\n\tbo.MaxElapsedTime = time.Second\n\tvar info *ContainerInfo\n\terr = backoff.Retry(func() error {\n\t\tci, err := cli.ContainerInspect(ctx, cid)\n\t\tif err != nil {\n\t\t\t// The container in question no longer exists\n\t\t\treturn backoff.Permanent(err)\n\t\t}\n\t\tvar iPv4, iPv6 netip.Addr\n\t\tif network != \"\" {\n\t\t\tns := ci.NetworkSettings\n\t\t\tif ns == nil {\n\t\t\t\treturn errdefs.ErrNotFound\n\t\t\t}\n\t\t\ttn, ok := ns.Networks[network]\n\t\t\tif ok {\n\t\t\t\tif dcfg.EnableIPv4 && tn.IPAddress != \"\" {\n\t\t\t\t\tiPv4, err = netip.ParseAddr(tn.IPAddress)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn backoff.Permanent(fmt.Errorf(\"failed to parse IPAddress of network %q: %w\", network, err))\n\t\t\t\t\t}\n\t\t\t\t\tclog.Debugf(ctx, \"container %q has IPv4 address %s in network %q\", ci.Name, iPv4, network)\n\t\t\t\t}\n\t\t\t\tif dcfg.EnableIPv6 && tn.GlobalIPv6Address != \"\" {\n\t\t\t\t\tiPv6, err = netip.ParseAddr(tn.GlobalIPv6Address)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn backoff.Permanent(fmt.Errorf(\"failed to parse GlobalIPv6Address of network %q: %w\", network, err))\n\t\t\t\t\t}\n\t\t\t\t\tclog.Debugf(ctx, \"container %q has IPv6 address %s in network %q\", ci.Name, iPv6, network)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !iPv4.IsValid() && !iPv6.IsValid() {\n\t\t\t\t// retry the operation if this happens\n\t\t\t\treturn fmt.Errorf(\"container %q has no IP address in network %q: %w\", ci.Name, network, errdefs.ErrNotFound)\n\t\t\t}\n\t\t}\n\t\tinfo = &ContainerInfo{ID: ci.ID, Pid: ci.State.Pid, IPv4: iPv4, IPv6: iPv6, Name: ci.Name}\n\t\treturn nil\n\t}, backoff.WithContext(bo, ctx))\n\treturn info, err\n}\n\nfunc UseIPv6(ctx context.Context) (bool, error) {\n\tdcfg := client.GetConfig(ctx).Docker()\n\tif !dcfg.EnableIPv6 {\n\t\treturn false, nil\n\t}\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tci, err := cli.NetworkInspect(ctx, \"bridge\", network.InspectOptions{})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn ci.EnableIPv6, nil\n}\n\nfunc readPortFile(ctx context.Context, portFile string, configFiles []string) (uint16, error) {\n\tpb, err := os.ReadFile(portFile)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tvar p kubeauth.PortFile\n\terr = json.Unmarshal(pb, &p)\n\tif err == nil {\n\t\tif p.Kubeconfig == strings.Join(configFiles, string(filepath.ListSeparator)) {\n\t\t\treturn uint16(p.Port), nil\n\t\t}\n\t\tclog.Debug(ctx, \"kubeconfig used by kubeauth is no longer valid\")\n\t}\n\tif err := os.Remove(portFile); err != nil {\n\t\treturn 0, err\n\t}\n\treturn 0, fmt.Errorf(\"%s: %w\", portFile, os.ErrNotExist)\n}\n\nfunc startAuthenticatorService(ctx context.Context, portFile string, kubeFlags map[string]string, configFiles []string) (uint16, error) {\n\tclog.Debugf(ctx, \"Starting authenticator service using portFile %s\", portFile)\n\t// remove any stale port file\n\t_ = os.Remove(portFile)\n\n\targs := make([]string, 0, 6+len(kubeFlags)*2)\n\targs = append(args, client.GetExe(ctx), client.KubeAuthDaemonName, \"--\"+global.FlagConfig, client.GetConfigFile(ctx), \"--portfile\", portFile)\n\tvar err error\n\tif args, err = k8s.AppendKubeFlags(kubeFlags, args); err != nil {\n\t\treturn 0, err\n\t}\n\tif err := proc.StartInBackground(true, args...); err != nil {\n\t\treturn 0, err\n\t}\n\n\t// Wait for the new port file to emerge\n\tctx, cancel := context.WithTimeout(ctx, 3*time.Second)\n\tdefer cancel()\n\tfor ctx.Err() == nil {\n\t\ttime.Sleep(10 * time.Millisecond)\n\t\tport, err := readPortFile(ctx, portFile, configFiles)\n\t\tif err != nil {\n\t\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tclog.Debugf(ctx, \"Authenticator service started on port %d\", port)\n\t\treturn port, nil\n\t}\n\treturn 0, fmt.Errorf(`timeout while waiting for \"%s %s\" to create a port file`, client.GetExe(ctx), client.KubeAuthDaemonName)\n}\n\nfunc ensureAuthenticatorService(ctx context.Context, kubeFlags map[string]string, configFiles []string) (uint16, error) {\n\tportFile := filepath.Join(filelocation.AppUserCacheDir(ctx), kubeAuthPortFile)\n\tst, err := os.Stat(portFile)\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn 0, err\n\t\t}\n\t} else if st.ModTime().Add(kubeauth.PortFileStaleTime).After(time.Now()) {\n\t\tport, err := readPortFile(ctx, portFile, configFiles)\n\t\tif err == nil {\n\t\t\tclog.Debug(ctx, \"kubeauth service found alive and valid\")\n\t\t\treturn port, nil\n\t\t}\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\treturn startAuthenticatorService(ctx, portFile, kubeFlags, configFiles)\n}\n\nfunc enableK8SAuthenticator(ctx context.Context, daemonID *daemon.Identifier) error {\n\tcr := daemon.GetRequest(ctx)\n\tif cr.Implicit {\n\t\treturn nil\n\t}\n\tif len(cr.KubeconfigData) > 0 {\n\t\t// Been there, done that\n\t\treturn nil\n\t}\n\tloader, err := k8s.ConfigLoader(ctx, cr.KubeFlags, cr.KubeconfigData)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcontent, err := patcher.CreateExternalKubeConfig(ctx, loader, cr.KubeFlags[\"context\"],\n\t\tfunc(configFiles []string) (string, string, string, error) {\n\t\t\tport, err := ensureAuthenticatorService(ctx, cr.KubeFlags, configFiles)\n\t\t\tif err != nil {\n\t\t\t\tclog.Errorf(ctx, \"failed to start k8s authenticator service: %v\", err)\n\t\t\t\treturn \"\", \"\", \"\", err\n\t\t\t}\n\n\t\t\t// The telepresence command that will run in order to retrieve the credentials from the authenticator service\n\t\t\t// will run in a container, so the first argument must be a path that finds the telepresence executable and\n\t\t\t// the second must be an address that will find the host's port, not the container's localhost. The host\n\t\t\t// in this case is the client performing the authentication (as opposed to the Docker VM, when one is used).\n\t\t\tcfg := client.GetConfig(ctx).Docker()\n\t\t\tkubeAuthHost := cfg.HostGateway\n\t\t\tif kubeAuthHost == \"\" {\n\t\t\t\tr, err := routing.DefaultRoute(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", \"\", \"\", err\n\t\t\t\t}\n\t\t\t\tkubeAuthHost = r.LocalIP.String()\n\t\t\t}\n\t\t\treturn \"telepresence\", iputil.JoinHostPort(kubeAuthHost, port), filepath.Join(DockerTpConfig, filepath.Base(client.GetConfigFile(ctx))), nil\n\t\t},\n\t\tfunc(config *api.Config) error {\n\t\t\treturn handleLocalK8s(ctx, daemonID, config)\n\t\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tcr.KubeconfigData = content\n\treturn nil\n}\n\n// handleLocalK8s checks if the cluster is using a well-known provider (currently minikube or kind)\n// and if so, ensures that the daemon container is connected to its network.\nfunc handleLocalK8s(ctx context.Context, daemonID *daemon.Identifier, config *api.Config) error {\n\tcc := config.Contexts[config.CurrentContext]\n\tcl := config.Clusters[cc.Cluster]\n\tserver, err := url.Parse(cl.Server)\n\tif err != nil {\n\t\treturn err\n\t}\n\thost, portStr, err := net.SplitHostPort(server.Host)\n\tif err != nil {\n\t\t// Host doesn't have a port, so it's not a local k8s.\n\t\treturn nil\n\t}\n\taddr, err := netip.ParseAddr(host)\n\tif err != nil {\n\t\tif host != \"localhost\" {\n\t\t\t// Address is not a valid IP address, so it's not a local k8s. Docker can't make\n\t\t\t// containers available to the host via DNS.\n\t\t\treturn nil\n\t\t}\n\t\taddr = netip.AddrFrom4([4]byte{127, 0, 0, 1})\n\t}\n\n\tport, err := strconv.ParseUint(portStr, 10, 16)\n\tif err != nil {\n\t\treturn nil\n\t}\n\taddrPort := netip.AddrPortFrom(addr, uint16(port))\n\n\t// Let's check if we have a container with port bindings for the\n\t// given addrPort that is a known k8sapi provider\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tcjs := runningContainers(ctx, cli)\n\n\thostPort, nw := detectControlPlane(ctx, cli, cjs, addrPort)\n\tif hostPort.IsValid() {\n\t\tserver.Host = hostPort.String()\n\t\tcl.Server = server.String()\n\t} else if addrPort.Addr().IsLoopback() {\n\t\t// We're running in a container, but apparently the control-plan isn't. Since we can't use the host's loopback interface directly,\n\t\t// the best we can do here is to use the \"host.docker.internal\" (or whatever alias the user has configured for the GatewayHost) and\n\t\t// hope that the server's certificate is configured to accept connections from that address.\n\t\tserver.Host = iputil.JoinHostPort(client.GetConfig(ctx).Docker().HostGateway, addrPort.Port())\n\t\tclog.Debugf(ctx, \"Connecting to host's %s via alias %s\", addrPort, server.Host)\n\t\tcl.Server = server.String()\n\t}\n\n\tif nw != \"\" {\n\t\tdcName := daemonID.ContainerName()\n\t\tclog.Debugf(ctx, \"Connecting network %s to container %s\", nw, dcName)\n\t\tif err = cli.NetworkConnect(ctx, nw, dcName, nil); err != nil {\n\t\t\tif !strings.Contains(err.Error(), \"already exists\") {\n\t\t\t\tclog.Debugf(ctx, \"failed to connect network %s to container %s: %v\", nw, dcName, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// LaunchDaemon ensures that the image returned by ClientImage exists by calling PullImage. It then uses the\n// options DaemonOptions and DaemonArgs to start the image, and finally connectDaemon to connect to it. A\n// successful start yields a cache.Info entry in the cache.\nfunc LaunchDaemon(ctx context.Context, daemonID *daemon.Identifier) (info *daemon.Info, conn *grpc.ClientConn, err error) {\n\timage := ClientImage(ctx)\n\tif err = PullImage(progress.WithEventId(ctx, daemonID.Name), image); err != nil {\n\t\treturn nil, nil, errcat.NoDaemonLogs.New(err)\n\t}\n\tfp, err := ioutil.FreePortsTCP(1)\n\tif err != nil {\n\t\treturn nil, nil, errcat.NoDaemonLogs.New(err)\n\t}\n\tdaemonAddr := fp[0]\n\topts, err := DaemonOptions(ctx, daemonID, daemonAddr.Port())\n\tif err != nil {\n\t\treturn nil, nil, errcat.NoDaemonLogs.New(err)\n\t}\n\targs := DaemonArgs(ctx, daemonID)\n\n\tallArgs := make([]string, 0, len(opts)+len(args)+4)\n\tallArgs = append(allArgs,\n\t\t\"run\",\n\t\t\"--rm\",\n\t\t\"-d\",\n\t)\n\tallArgs = append(allArgs, opts...)\n\tallArgs = append(allArgs, image)\n\tallArgs = append(allArgs, args...)\n\tstopAttempted := false\n\tfor i := 1; ; i++ {\n\t\tinfo, err = tryLaunch(ctx, daemonID, daemonAddr.Port(), allArgs)\n\t\tif err != nil {\n\t\t\tif !strings.Contains(err.Error(), \"already in use by container\") {\n\t\t\t\treturn nil, nil, errcat.NoDaemonLogs.New(err)\n\t\t\t}\n\t\t\t// This may happen if the daemon has died (and hence, we never discovered it), but\n\t\t\t// the container still hasn't died. Let's sleep for a short while and retry.\n\t\t\tif i < 6 {\n\t\t\t\ttime.Sleep(time.Duration(i) * 500 * time.Millisecond)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif stopAttempted {\n\t\t\t\treturn nil, nil, err\n\t\t\t}\n\t\t\t// The Container is still alive. Try and stop it.\n\t\t\t_ = StopContainer(ctx, daemonID.ContainerName())\n\t\t\tstopAttempted = true\n\t\t\ti = 1\n\t\t\tcontinue\n\t\t}\n\t\tbreak\n\t}\n\n\tif err = enableK8SAuthenticator(ctx, daemonID); err != nil {\n\t\treturn nil, nil, err\n\t}\n\tconn, err = ConnectDaemon(ctx, info)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\treturn info, conn, nil\n}\n\n// containerPort returns the port that the container uses internally to expose the given\n// addrPort on the host. Zero is returned when the addrPort is not found among\n// the container's port bindings.\n// The additional bool is true if the host address is IPv6.\nfunc containerPort(addrPort netip.AddrPort, ns *container.NetworkSettings) (port uint16, isIPv6 bool) {\n\t// If the port mapping exists where the source address is the host's address, then use the destination port.\n\tfor portDef, bindings := range ns.Ports {\n\t\tif portDef.Proto() != \"tcp\" {\n\t\t\tcontinue\n\t\t}\n\t\tfor _, binding := range bindings {\n\t\t\taddr, err := netip.ParseAddr(binding.HostIP)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpn, err := strconv.ParseUint(binding.HostPort, 10, 16)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif netip.AddrPortFrom(addr, uint16(pn)) == addrPort {\n\t\t\t\treturn uint16(portDef.Int()), addr.Is6()\n\t\t\t}\n\t\t}\n\t}\n\n\t// If the address on the host belongs to a network, then trust the current port.\n\taddr := addrPort.Addr()\n\tfor _, nw := range ns.Networks {\n\t\tif ic := nw.IPAMConfig; ic != nil {\n\t\t\tif addr.Is4() && ic.IPv4Address != \"\" {\n\t\t\t\tna, err := netip.ParseAddr(ic.IPv4Address)\n\t\t\t\tif err == nil && addr == na {\n\t\t\t\t\treturn addrPort.Port(), false\n\t\t\t\t}\n\t\t\t}\n\t\t\tif addr.Is6() && ic.IPv6Address != \"\" {\n\t\t\t\tna, err := netip.ParseAddr(ic.IPv6Address)\n\t\t\t\tif err == nil && addr == na {\n\t\t\t\t\treturn addrPort.Port(), false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn 0, false\n}\n\n// runningContainers returns the inspect data for all containers with status=running.\nfunc runningContainers(ctx context.Context, cli dockerClient.APIClient) []*container.InspectResponse {\n\tcl, err := cli.ContainerList(ctx, container.ListOptions{\n\t\tFilters: filters.NewArgs(filters.KeyValuePair{Key: \"status\", Value: \"running\"}),\n\t})\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"failed to list containers: %v\", err)\n\t\treturn nil\n\t}\n\tcjs := make([]*container.InspectResponse, 0, len(cl))\n\tfor _, cn := range cl {\n\t\tcj, err := cli.ContainerInspect(ctx, cn.ID)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"container inspect on %v failed: %v\", cn.Names, err)\n\t\t} else {\n\t\t\tcjs = append(cjs, &cj)\n\t\t}\n\t}\n\treturn cjs\n}\n\nfunc endpointAddr(cn *network.EndpointResource, isIPv6 bool) (addr netip.Addr, _ error) {\n\t// These aren't IP-addresses at all. They are prefixes!\n\tvar prefix string\n\tif isIPv6 {\n\t\tprefix = cn.IPv6Address\n\t} else {\n\t\tprefix = cn.IPv4Address\n\t}\n\tap, err := netip.ParsePrefix(prefix)\n\tif err == nil {\n\t\taddr = ap.Addr()\n\t}\n\treturn addr, err\n}\n\nfunc localAddr(ctx context.Context, cli dockerClient.APIClient, cnID, nwID string, isIPv6 bool) (addr netip.Addr, err error) {\n\tnw, err := cli.NetworkInspect(ctx, nwID, network.InspectOptions{})\n\tif err != nil {\n\t\treturn addr, err\n\t}\n\tif cn, ok := nw.Containers[cnID]; ok {\n\t\t// These aren't IP-addresses at all. They are prefixes!\n\t\treturn endpointAddr(&cn, isIPv6)\n\t}\n\treturn addr, errors.New(\"no such container\")\n}\n\nfunc findNetworkSettingsForHostPort(cns []*container.InspectResponse, hostAddrPort netip.AddrPort) (*container.InspectResponse, uint16, bool) {\n\tfor _, cn := range cns {\n\t\tif ns := cn.NetworkSettings; ns != nil {\n\t\t\tif port, isIPv6 := containerPort(hostAddrPort, ns); port != 0 {\n\t\t\t\treturn cn, port, isIPv6\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, 0, false\n}\n\ntype containerFilter func(cn *container.InspectResponse) bool\n\n//nolint:gochecknoglobals // constant\nvar knownFilters = map[string]containerFilter{\n\t\"minikube\": func(cn *container.InspectResponse) bool {\n\t\treturn cn.Config != nil && cn.Config.Labels[\"name.minikube.sigs.k8s.io\"] != \"\"\n\t},\n\t\"k3s\": func(cn *container.InspectResponse) bool {\n\t\treturn cn.Config != nil && strings.Contains(cn.Config.Image, \"/k3s:\")\n\t},\n\t\"kind\": func(cn *container.InspectResponse) bool {\n\t\treturn cn.Config != nil && cn.Config.Labels[\"io.x-k8s.kind.role\"] == \"control-plane\"\n\t},\n}\n\nfunc detectControlPlane(ctx context.Context, cli dockerClient.APIClient, cns []*container.InspectResponse, hostAddr netip.AddrPort) (ap netip.AddrPort, nn string) {\n\tncn, port, isIPv6 := findNetworkSettingsForHostPort(cns, hostAddr)\n\tif ncn == nil {\n\t\tclog.Debugf(ctx, \"no network settings found that maps host address %s\", hostAddr)\n\t\treturn ap, nn\n\t}\n\n\ttype candidate struct {\n\t\tcontainer   *container.InspectResponse\n\t\tnetworkName string\n\t\tlocalAddr   netip.AddrPort\n\t}\n\n\tns := ncn.NetworkSettings\n\tcandidates := make([]candidate, 0)\n\tfor _, cn := range cns {\n\t\tfor networkName, nw := range ns.Networks {\n\t\t\taddr, err := localAddr(ctx, cli, cn.ID, nw.NetworkID, isIPv6)\n\t\t\tif err == nil {\n\t\t\t\tcandidates = append(candidates, candidate{\n\t\t\t\t\tcontainer:   cn,\n\t\t\t\t\tnetworkName: networkName,\n\t\t\t\t\tlocalAddr:   netip.AddrPortFrom(addr, port),\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\tswitch len(candidates) {\n\tcase 0:\n\t\tbreak\n\tcase 1:\n\t\tc := candidates[0]\n\t\tclog.Debugf(ctx, \"found control-plane %s(%s) for host address %s on network %q\", c.container.Name, c.localAddr, hostAddr, c.networkName)\n\t\treturn c.localAddr, c.networkName\n\tdefault:\n\t\t// We have multiple candidates. Let's try and discriminate using the known filters.'\n\t\tfor _, c := range candidates {\n\t\t\tfor filterName, filter := range knownFilters {\n\t\t\t\tif filter(c.container) {\n\t\t\t\t\tclog.Debugf(ctx, \"found control-plane %s(%s) for host address %s on network %q using filter %q\",\n\t\t\t\t\t\tc.container.Name, c.localAddr, hostAddr, c.networkName, filterName)\n\t\t\t\t\treturn c.localAddr, c.networkName\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tclog.Debugf(ctx, \"found no control-plane for host address %s, container %s, container port %d\", hostAddr, ncn.Name, port)\n\treturn ap, nn\n}\n\nfunc tryLaunch(ctx context.Context, daemonID *daemon.Identifier, port uint16, args []string) (*daemon.Info, error) {\n\tstdErr := bytes.Buffer{}\n\tstdOut := bytes.Buffer{}\n\tclog.Debug(ctx, shellquote.ShellString(Exe, args))\n\tcmd := proc.CommandContext(ctx, Exe, args...)\n\tcmd.Stderr = &stdErr\n\tcmd.Stdout = &stdOut\n\terr := cmd.Run()\n\tcid := strings.TrimSpace(stdOut.String())\n\terrStr := strings.TrimSpace(stdErr.String())\n\tif errStr != \"\" || err != nil {\n\t\terr = fmt.Errorf(\"launch of daemon container failed: %s%s: %w\", cid, errStr, err)\n\t\tclog.Error(ctx, err)\n\t\treturn nil, err\n\t}\n\n\t// The teleroute network plugin communicates with the daemon over the default bridge network\n\tcni, err := GetContainerInfo(ctx, cid, \"bridge\")\n\tif err != nil {\n\t\tprogress.Error(ctx, err.Error())\n\t\treturn nil, err\n\t}\n\tcr := daemon.MustGetRequest(ctx)\n\tclog.Debugf(ctx, \"Creating daemon info file %s (runs in container)\", daemonID.Name)\n\tvar ip netip.Addr\n\tif cni.IPv4.IsValid() {\n\t\tip = cni.IPv4\n\t} else {\n\t\tip = cni.IPv6\n\t}\n\tinfo := &daemon.Info{\n\t\tContainerID:  cid,\n\t\tContainerPID: cni.Pid,\n\t\tContainerIP:  ip,\n\t\tDaemonPort:   port,\n\t\tName:         daemonID.Name,\n\t\tKubeContext:  daemonID.KubeContext,\n\t\tNamespace:    daemonID.Namespace,\n\t\tExposedPorts: cr.ExposedPorts,\n\t\tHostname:     cr.Hostname,\n\t}\n\treturn info, daemon.NewUserInfoLoader(ctx).SaveInfo(info, daemonID.InfoFileName())\n}\n\nfunc WaitForExit(ctx context.Context, cli *dockerClient.Client, id string, maxTime time.Duration) error {\n\tconst exitPollInterval = 200 * time.Millisecond\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil\n\tcase <-time.After(exitPollInterval):\n\t}\n\tstillRunning := fmt.Errorf(\"container %s is still running\", id)\n\topts := container.ListOptions{Filters: filters.NewArgs(filters.Arg(\"id\", id))}\n\treturn backoff.Retry(func() error {\n\t\tlst, err := cli.ContainerList(ctx, opts)\n\t\tif err != nil {\n\t\t\terr = backoff.Permanent(err)\n\t\t} else if len(lst) > 0 {\n\t\t\terr = stillRunning\n\t\t}\n\t\treturn err\n\t}, backoff.WithContext(backoff.NewExponentialBackOff(backoff.WithInitialInterval(exitPollInterval), backoff.WithMaxElapsedTime(maxTime)), ctx))\n}\n\nfunc appendOSSpecificContainerOpts(ctx context.Context, opts []string) ([]string, error) {\n\tif proc.RunningInWSL() {\n\t\t// Using host.docker.internal:host-gateway won't work for the kubeauth process, because Windows Docker Desktop\n\t\t// will assign the IP of the Windows host, not the host from where this process was started (the Linux host).\n\t\t// We'll reach that using the gateway of the default host.\n\t\tr, err := routing.DefaultRoute(ctx)\n\t\tif err != nil {\n\t\t\treturn opts, err\n\t\t}\n\t\topts = append(opts, \"-e\", fmt.Sprintf(\"TELEPRESENCE_KUBEAUTH_HOST=%s\", r.LocalIP))\n\t}\n\treturn opts, nil\n}\n"
  },
  {
    "path": "pkg/client/docker/daemon_test.go",
    "content": "package docker\n\nimport (\n\t\"testing\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\nfunc TestSafeContainerName(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\twant string\n\t}{\n\t\t{\n\t\t\t\"@\",\n\t\t\t\"a\",\n\t\t},\n\t\t{\n\t\t\t\"@x\",\n\t\t\t\"ax\",\n\t\t},\n\t\t{\n\t\t\t\"x@\",\n\t\t\t\"x_\",\n\t\t},\n\t\t{\n\t\t\t\"x@y\",\n\t\t\t\"x_y\",\n\t\t},\n\t\t{\n\t\t\t\"x™y\", // multibyte char\n\t\t\t\"x_y\",\n\t\t},\n\t\t{\n\t\t\t\"x™\", // multibyte char\n\t\t\t\"x_\",\n\t\t},\n\t\t{\n\t\t\t\"_y\",\n\t\t\t\"ay\",\n\t\t},\n\t\t{\n\t\t\t\"_y_\",\n\t\t\t\"ay_\",\n\t\t},\n\t\t// TODO: Add test cases.\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := ioutil.SafeName(tt.name); got != tt.want {\n\t\t\t\tt.Errorf(\"SafeName() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/client/docker/image.go",
    "content": "package docker\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/progress\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\n// BuildImage builds an image from source. Stdout is silenced during those operations. The\n// image ID is returned.\nfunc BuildImage(ctx context.Context, context string, buildArgs []string) (string, error) {\n\targs := append([]string{\"build\", \"--quiet\"}, buildArgs...)\n\tst, err := os.Stat(context)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif st.Mode().IsRegular() {\n\t\tvar fn string\n\t\tdir := filepath.Dir(context)\n\t\tif dir == \".\" {\n\t\t\tfn = context\n\t\t} else {\n\t\t\tfn, err = filepath.Abs(context)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t}\n\t\tcontext = dir\n\t\targs = append(args, \"--file\", fn)\n\t}\n\tcmd := proc.StdCommand(ctx, Exe, append(args, context)...)\n\tvar out bytes.Buffer\n\tcmd.Stdout = &out\n\tif err := cmd.Run(); err != nil {\n\t\treturn \"\", err\n\t}\n\treturn strings.TrimSpace(out.String()), nil\n}\n\n// PullImage checks if the given image exists locally by doing docker image inspect. A docker pull is\n// performed if no local image is found. Stdout is silenced during those operations.\nfunc PullImage(ctx context.Context, image string) error {\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = cli.ImageInspect(ctx, image)\n\tif err == nil {\n\t\t// Image exists in the local cache, so don't bother pulling it.\n\t\treturn nil\n\t}\n\tprogress.Working(ctx, \"Pulling image \"+image)\n\tcmd := proc.StdCommand(ctx, Exe, \"pull\", image)\n\t// Docker run will put the pull logs in stderr, but docker pull will put them in stdout.\n\t// We discard them here, so they don't spam the user. They'll get errors through stderr if it comes to it.\n\tcmd.Stdout = io.Discard\n\n\t// Only print stderr if the return code is non-zero\n\tvar stderr bytes.Buffer\n\tcmd.Stderr = &stderr\n\n\terr = cmd.Run()\n\tif err != nil {\n\t\treturn progress.MaybeWriteError(ctx, err)\n\t}\n\tprogress.Done(ctx, \"Pulled image \"+image)\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/docker/kubeauth/cmd.go",
    "content": "package kubeauth\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"net\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\t\"github.com/telepresenceio/clog\"\n\tauthGrpc \"github.com/telepresenceio/telepresence/v2/pkg/authenticator/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/server\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/sigctx\"\n)\n\nconst (\n\tPortFileStaleTime = 3 * time.Second\n\tlogfileFlag       = \"logfile\"\n)\n\ntype authService struct {\n\tportFile     string\n\tkubeFlags    *genericclioptions.ConfigFlags\n\tconfigFiles  []string\n\tclientConfig clientcmd.ClientConfig\n}\n\ntype PortFile struct {\n\tPort       int    `json:\"port\"`\n\tKubeconfig string `json:\"kubeconfig\"`\n}\n\nfunc Command(ctx context.Context) *cobra.Command {\n\tas := authService{kubeFlags: genericclioptions.NewConfigFlags(false)}\n\tc := &cobra.Command{\n\t\tUse:    client.KubeAuthDaemonName,\n\t\tShort:  \"Launch Telepresence Kubernetes Authenticator Daemon\",\n\t\tArgs:   cobra.NoArgs,\n\t\tHidden: true,\n\t\tRunE:   as.run,\n\t}\n\tflags := c.Flags()\n\tflags.StringVar(&as.portFile, \"portfile\", \"\", \"File where server existence is announced.\")\n\tflags.String(logfileFlag, filepath.Join(filelocation.AppUserLogDir(ctx), \"kubeauth.log\"),\n\t\t`Log file to write to { <path to a file> | \"stdout\" | \"stderr\" | \"-\" (same as \"stderr\") }`)\n\tas.kubeFlags.AddFlags(flags)\n\treturn c\n}\n\nfunc (as *authService) run(cmd *cobra.Command, _ []string) error {\n\treturn sigctx.DoWithSignalHandler(cmd.Context(), func(ctx context.Context) error { return as.internalRun(ctx, cmd.Flags()) })\n}\n\nfunc (as *authService) internalRun(ctx context.Context, flags *pflag.FlagSet) error {\n\tcfg, err := client.LoadConfig(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tctx = client.WithConfig(ctx, cfg)\n\n\tif as.portFile == \"\" {\n\t\treturn errcat.User.New(\"missing required flag --portfile\")\n\t}\n\tgrpcListener, err := net.Listen(\"tcp\", \":0\")\n\tif err != nil {\n\t\treturn errcat.NoDaemonLogs.Errorf(err, \"unable to open a port on localhost\")\n\t}\n\n\tlogFile := flags.Lookup(logfileFlag).Value.String()\n\tctx, err = logging.InitContext(ctx, logFile, cfg.LogLevels().KubeAuthDaemon, logging.RotateNever, false)\n\tif err != nil {\n\t\treturn err\n\t}\n\taddr := grpcListener.Addr().(*net.TCPAddr)\n\tclog.Infof(ctx, \"kubeauth daemon listening on address %s\", addr)\n\n\tas.clientConfig = as.kubeFlags.ToRawKubeConfigLoader()\n\tas.configFiles = as.clientConfig.ConfigAccess().GetLoadingPrecedence()\n\tp := PortFile{\n\t\tPort:       addr.Port,\n\t\tKubeconfig: strings.Join(as.configFiles, string(filepath.ListSeparator)),\n\t}\n\tpb, err := json.Marshal(&p)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = os.WriteFile(as.portFile, pb, 0o644); err != nil {\n\t\treturn err\n\t}\n\n\tg := log.NewGroup(ctx)\n\tg.Go(\"portfile-alive\", as.keepPortFileAlive)\n\tg.Go(\"portfile-watcher\", as.watchFiles)\n\tg.Go(\"grpc-server\", func(ctx context.Context) error {\n\t\tsvc := server.New(ctx)\n\t\tauthGrpc.RegisterAuthenticatorServer(svc, as)\n\t\treturn server.Serve(ctx, svc, grpcListener)\n\t})\n\tif err = g.Wait(); err != nil {\n\t\tclog.Errorf(ctx, \"kubeauth daemon exiting with error: %v\", err)\n\t} else {\n\t\tclog.Info(ctx, \"kubeauth daemon exiting\")\n\t}\n\treturn err\n}\n\nfunc (as *authService) ClientConfig() (clientcmd.ClientConfig, error) {\n\treturn as.clientConfig, nil\n}\n\nfunc (as *authService) keepPortFileAlive(ctx context.Context) error {\n\tticker := time.NewTicker(PortFileStaleTime)\n\tdefer func() {\n\t\tticker.Stop()\n\t\t_ = os.Remove(as.portFile)\n\t\tclog.Debugf(ctx, \"kubeauth daemon removed %s\", as.portFile)\n\t}()\n\tnow := time.Now()\n\tfor {\n\t\tif err := os.Chtimes(as.portFile, now, now); err != nil {\n\t\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\t\treturn fmt.Errorf(\"failed to update timestamp on %s: %v\", as.portFile, err)\n\t\t\t}\n\t\t\t// File is removed, so stop trying to update its timestamps and die\n\t\t\treturn nil\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tcase now = <-ticker.C:\n\t\t}\n\t}\n}\n\nfunc (as *authService) watchFiles(ctx context.Context) error {\n\t// If any of the files that the current kubeconfig uses change, then we die\n\tfiles := as.configFiles\n\n\t// If the portFile changes, then we die\n\tfiles = append(files, as.portFile)\n\n\tdirs := make(map[string]struct{})\n\tfor _, file := range files {\n\t\tdir := filepath.Dir(file)\n\t\tdirs[dir] = struct{}{}\n\t}\n\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer watcher.Close()\n\n\tisOfInterest := func(s string, files []string) bool {\n\t\tfor _, file := range files {\n\t\t\tif s == file {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\tfor dir := range dirs {\n\t\t// Can't watch things that don't exist. We want to know if files in there change, though.\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = watcher.Add(dir); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tcase err = <-watcher.Errors:\n\t\t\tclog.Error(ctx, err)\n\t\tcase event := <-watcher.Events:\n\t\t\tif event.Op&(fsnotify.Remove|fsnotify.Write|fsnotify.Create) != 0 && isOfInterest(event.Name, files) {\n\t\t\t\tclog.Infof(ctx, \"Terminated due to %s in %s\", event.Op, event.Name)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/docker/plugin.go",
    "content": "package docker\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/containerd/errdefs\"\n\tdockerTypes \"github.com/docker/docker/api/types\"\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cache\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nconst (\n\tpluginTypeVolume  = \"volume\"\n\tpluginTypeNetwork = \"network\"\n)\n\n// EnsureVolumePlugin checks if the telemount plugin is installed and installs it if that is\n// not the case. The plugin is also enabled.\nfunc EnsureVolumePlugin(ctx context.Context) (string, error) {\n\tcfg := client.DockerImage(client.GetConfig(ctx).Docker().Telemount)\n\treturn ensurePlugin(ctx, pluginTypeVolume, &cfg)\n}\n\n// EnsureNetworkPlugin checks if the telemount plugin is installed and installs it if that is\n// not the case. The plugin is also enabled.\nfunc EnsureNetworkPlugin(ctx context.Context) (string, error) {\n\tcfg := client.DockerImage(client.GetConfig(ctx).Docker().Teleroute)\n\treturn ensurePlugin(ctx, pluginTypeNetwork, &cfg)\n}\n\nfunc NetworkPluginName(ctx context.Context) string {\n\tcfg := client.DockerImage(client.GetConfig(ctx).Docker().Teleroute)\n\treturn latestPluginName(ctx, &cfg, pluginTypeNetwork)\n}\n\nfunc ensurePlugin(ctx context.Context, pluginType string, cfg *client.DockerImage) (string, error) {\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tpn := latestPluginName(ctx, cfg, pluginType)\n\tpi, _, err := cli.PluginInspectWithRaw(ctx, pn)\n\tif err != nil {\n\t\tif !errdefs.IsNotFound(err) {\n\t\t\tclog.Errorf(ctx, \"docker plugin inspect: %v\", err)\n\t\t}\n\t\treturn pn, installPlugin(ctx, pn)\n\t}\n\tif !pi.Enabled {\n\t\terr = cli.PluginEnable(ctx, pn, dockerTypes.PluginEnableOptions{Timeout: 5})\n\t}\n\tclog.Debugf(ctx, \"using %s plugin: %s\", pluginType, pn)\n\treturn pn, err\n}\n\nfunc pluginName(tm *client.DockerImage) string {\n\treturn fmt.Sprintf(\"%s/%s/%s:%s\", tm.Registry, tm.Namespace, tm.Repository, runtime.GOARCH)\n}\n\nfunc latestPluginName(ctx context.Context, cfg *client.DockerImage, pluginType string) string {\n\tpn := pluginName(cfg)\n\tif pt := cfg.Tag; pt != \"\" {\n\t\tpn += \"-\" + pt\n\t} else if lv, err := latestPluginVersion(ctx, pn, pluginType, cfg); err == nil {\n\t\tpn += \"-\" + lv.String()\n\t} else {\n\t\tclog.Warnf(ctx, \"failed to get latest version of docker %s plugin %s: %v\", pluginType, pn, err)\n\t}\n\treturn pn\n}\n\nfunc installPlugin(ctx context.Context, pluginName string) error {\n\tclog.Debugf(ctx, \"Installing docker plugin %s\", pluginName)\n\tcmd := proc.CommandContext(ctx, Exe, \"plugin\", \"install\", \"--grant-all-permissions\", pluginName)\n\t_, err := proc.CaptureErr(cmd)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"%s plugin install %s: %w\", Exe, pluginName, err)\n\t}\n\treturn err\n}\n\ntype pluginInfo struct {\n\tLatestVersion string `json:\"latestVersions\"`\n\tLastCheck     int64  `json:\"lastCheck\"`\n}\n\nconst pluginInfoMaxAge = 24 * time.Hour\n\nvar zeroVersion = semver.Version{} //nolint:gochecknoglobals // constant\n\nfunc latestPluginVersion(ctx context.Context, pluginName, pluginType string, cfg *client.DockerImage) (ver semver.Version, err error) {\n\tfile := pluginType + \"-plugin-info.json\"\n\tpi := pluginInfo{}\n\tif err = cache.LoadFromUserCache(ctx, &pi, file); err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn ver, err\n\t\t}\n\t\tpi.LastCheck = 0\n\t}\n\n\tnow := time.Now().UnixNano()\n\tif time.Duration(now-pi.LastCheck) > pluginInfoMaxAge {\n\t\tver, err = getLatestPluginVersion(ctx, pluginName, cfg)\n\t\tif err == nil && !ver.EQ(zeroVersion) {\n\t\t\tpi.LatestVersion = ver.String()\n\t\t\tpi.LastCheck = now\n\t\t\terr = cache.SaveToUserCache(ctx, &pi, file, cache.Public)\n\t\t}\n\t} else {\n\t\tclog.Debugf(ctx, \"Using cached version %s for %s\", pi.LatestVersion, pluginName)\n\t\tver, err = semver.Parse(pi.LatestVersion)\n\t}\n\treturn ver, err\n}\n\ntype imgResult struct {\n\tName string `json:\"name\"`\n}\ntype repsResponse struct {\n\tResults []imgResult `json:\"results\"`\n}\n\nfunc getLatestPluginVersion(ctx context.Context, pluginName string, cfg *client.DockerImage) (ver semver.Version, err error) {\n\tclog.Debugf(ctx, \"Checking for latest version of %s\", pluginName)\n\ttag := cfg.Tag\n\tif tag == \"debug\" {\n\t\treturn zeroVersion, nil\n\t}\n\tif tag != \"\" {\n\t\treturn semver.Parse(tag)\n\t}\n\tif cfg.RegistryAPI == \"ghcr.io/v2\" {\n\t\treturn ver, fmt.Errorf(\"a tag for plugin %s must be set the client's docker config because the ghcr.io/v2 registry does not support anonymous queries\", pluginName)\n\t}\n\turl := fmt.Sprintf(\"https://%s/namespaces/%s/repositories/%s/tags\", cfg.RegistryAPI, cfg.Namespace, cfg.Repository)\n\tvar rq *http.Request\n\trq, err = http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn ver, err\n\t}\n\trq.Header.Add(\"Accept\", \"application/json\")\n\tvar rs *http.Response\n\trs, err = http.DefaultClient.Do(rq)\n\tif err != nil {\n\t\treturn ver, err\n\t}\n\tvar data []byte\n\tdata, err = io.ReadAll(rs.Body)\n\tif err != nil {\n\t\treturn ver, err\n\t}\n\t_ = rs.Body.Close()\n\tif rs.StatusCode != http.StatusOK {\n\t\treturn ver, errors.New(rs.Status)\n\t}\n\tvar infos repsResponse\n\terr = json.Unmarshal(data, &infos)\n\tif err != nil {\n\t\treturn ver, err\n\t}\n\tpfx := runtime.GOARCH + \"-\"\n\tfor _, info := range infos.Results {\n\t\tif strings.HasPrefix(info.Name, pfx) {\n\t\t\tiv, err := semver.Parse(strings.TrimPrefix(info.Name, pfx))\n\t\t\tif err == nil && iv.GT(ver) {\n\t\t\t\tver = iv\n\t\t\t}\n\t\t}\n\t}\n\tclog.Debugf(ctx, \"Found latest version of %s to be %s\", pluginName, ver)\n\treturn ver, err\n}\n"
  },
  {
    "path": "pkg/client/docker/teleroute/network.go",
    "content": "package teleroute\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/containerd/errdefs\"\n\t\"github.com/docker/docker/api/types/filters\"\n\t\"github.com/docker/docker/api/types/network\"\n\tdockerClient \"github.com/docker/docker/client\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n)\n\nconst daemonLabel = \"telepresence.io/teleroute/daemon\"\n\nfunc CreateNetwork(ctx context.Context, info *daemon.Info, cli *dockerClient.Client, teleroutePlugin string, teleroutePort uint16) error {\n\tcn := info.Name\n\tdockerCfg := client.GetConfig(ctx).Docker()\n\tipv4 := dockerCfg.EnableIPv4\n\tipv6, err := docker.UseIPv6(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\thost := info.ContainerIP\n\tif ipv6 && !ipv4 && host.Is4() {\n\t\thost = netip.AddrFrom16(host.As16())\n\t}\n\tif !ipv4 && !ipv6 {\n\t\treturn errcat.User.New(\"unable to create teleroute network because both the IPv4 and IPv6 families are disabled\")\n\t}\n\tclog.Debugf(ctx, \"Creating teleroute network %s\", cn)\n\trsp, err := cli.NetworkCreate(ctx, cn, network.CreateOptions{\n\t\tDriver:     teleroutePlugin,\n\t\tScope:      \"local\",\n\t\tInternal:   true,\n\t\tEnableIPv4: &ipv4,\n\t\tEnableIPv6: &ipv6,\n\t\tOptions: map[string]string{\n\t\t\t\"host\": host.String(),\n\t\t\t\"port\": strconv.Itoa(int(teleroutePort)),\n\t\t},\n\t\tLabels: map[string]string{\n\t\t\tdaemonLabel: info.ContainerID,\n\t\t},\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tif rsp.Warning != \"\" {\n\t\tclog.Warn(ctx, rsp.Warning)\n\t} else {\n\t\tclog.Debugf(ctx, \"Network %s created\", cn)\n\n\t\t// The daemon must be the first container to join the network. This join is special in that\n\t\t// it will not receive the routes that the daemon makes available. The connect is necessary\n\t\t// to open up for the daemon to communicate with other connected containers.\n\t\terr = cli.NetworkConnect(ctx, rsp.ID, info.ContainerID, &network.EndpointSettings{\n\t\t\tDriverOpts: map[string]string{\n\t\t\t\t\"daemon\": \"true\",\n\t\t\t},\n\t\t})\n\t}\n\treturn err\n}\n\n// IsTelerouteNetwork returns true if the network was created by the teleroute driver.\nfunc IsTelerouteNetwork(ctx context.Context, cli *dockerClient.Client, name string) bool {\n\tni, err := cli.NetworkInspect(ctx, name, network.InspectOptions{})\n\tif err != nil {\n\t\treturn false\n\t}\n\t_, ok := ni.Labels[daemonLabel]\n\treturn ok\n}\n\n// NetworkGC deletes zombie teleroute networks. A teleroute network is considered a zombie when:\n//\n//  1. It is not connected to a Telepresence daemon\n//  2. No container is currently connected to it.\nfunc NetworkGC(ctx context.Context, cli *dockerClient.Client) error {\n\tns, err := cli.NetworkList(ctx, network.ListOptions{Filters: filters.NewArgs(filters.KeyValuePair{\n\t\tKey:   \"label\",\n\t\tValue: daemonLabel,\n\t})})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, n := range ns {\n\t\t_, err := cli.ContainerInspect(ctx, n.Labels[daemonLabel])\n\t\tif errdefs.IsNotFound(err) {\n\t\t\tclog.Debugf(ctx, \"Garbage collecting network %s\", n.Name)\n\t\t\terr = cli.NetworkRemove(ctx, n.ID)\n\t\t\tif err != nil {\n\t\t\t\tee := err.Error()\n\t\t\t\tif ix := strings.Index(ee, \"has active endpoints\"); ix > 0 {\n\t\t\t\t\tclog.Debugf(ctx, \"Network %s was not garbage collected. It %s\", n.ID, ee[ix:])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// RemoveNetwork disconnects all containers that are connected to the network then removes the network.\nfunc RemoveNetwork(ctx context.Context, cli *dockerClient.Client, name string) (disconnected []string, err error) {\n\tni, err := cli.NetworkInspect(ctx, name, network.InspectOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif nc := len(ni.Containers); nc > 0 {\n\t\tclog.Debugf(ctx, \"Disconnecting %d containers from network %s\", nc, name)\n\t\tdisconnected = make([]string, 0, nc)\n\t\tfor cid := range ni.Containers {\n\t\t\terr = cli.NetworkDisconnect(ctx, name, cid, true)\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t} else {\n\t\t\t\tdisconnected = append(disconnected, cid)\n\t\t\t}\n\t\t}\n\t}\n\n\tclog.Debugf(ctx, \"Removing network %s\", name)\n\treturn disconnected, cli.NetworkRemove(ctx, name)\n}\n\nfunc ReconnectNetwork(ctx context.Context, cli *dockerClient.Client, name string, disconnected []string) {\n\tif nc := len(disconnected); nc > 0 {\n\t\tclog.Debugf(ctx, \"Reconnecting %d containers to network %s\", nc, name)\n\t\tfor _, cid := range disconnected {\n\t\t\terr := cli.NetworkConnect(ctx, name, cid, &network.EndpointSettings{})\n\t\t\tif err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/docker/teleroute/server.go",
    "content": "package teleroute\n\nimport \"net/netip\"\n\ntype Server interface {\n\t// DaemonAddresses returns the daemon's IP on the connected teleroute network. It will return an\n\t// Invalid address if the container hasn't been connected yet.\n\tDaemonAddresses() []netip.Addr\n}\n"
  },
  {
    "path": "pkg/client/docker/teleroute/server_linux.go",
    "content": "// Package teleroute contains the Telepresence Daemon Teleroute service that the Docker Network Plugin with the\n// same name connects to.\npackage teleroute\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base32\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\t\"github.com/vishvananda/netlink\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/teleroute\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\tgrpcServer \"github.com/telepresenceio/telepresence/v2/pkg/grpc/server\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\nconst bridgeName = \"br-daemon\"\n\ntype linkNotFoundError struct {\n\tname string\n\terr  error\n}\n\nfunc (e linkNotFoundError) Error() string {\n\treturn fmt.Sprintf(\"link %q not found: %s\", e.name, e.err.Error())\n}\n\nfunc (e linkNotFoundError) Unwrap() error {\n\treturn e.err\n}\n\ntype endpoint struct {\n\tmacAddr  net.HardwareAddr\n\tvethCont netlink.Link\n\tvethHost netlink.Link\n\tdaemon   bool\n}\n\ntype server struct {\n\trpc.UnsafeTelerouteServer\n\tdone           <-chan struct{}\n\twatchersMutex  sync.Mutex\n\tpluginPid      int\n\troutesCh       <-chan []netip.Prefix\n\tcurrentRoutes  []netip.Prefix\n\tgateways       []netip.Prefix\n\ttap            *vif.TunnelingDevice\n\tbridgeIdx      int\n\tendpoints      *xsync.Map[string, endpoint]\n\tendpointCache  *xsync.Map[netip.Addr, [2]netlink.Link]\n\tport           uint16\n\tdaemonAddrIPv4 netip.Addr\n\tdaemonAddrIPv6 netip.Addr\n}\n\nfunc StartServer(g log.Group, tap *vif.TunnelingDevice, routesCh <-chan []netip.Prefix, teleroutePort uint16) (Server, error) {\n\tts := &server{\n\t\troutesCh:      routesCh,\n\t\tdone:          make(chan struct{}),\n\t\ttap:           tap,\n\t\tport:          teleroutePort,\n\t\tendpoints:     xsync.NewMap[string, endpoint](),\n\t\tendpointCache: xsync.NewMap[netip.Addr, [2]netlink.Link](),\n\t}\n\tg.Go(\"teleroute\", ts.serve)\n\treturn ts, nil\n}\n\nfunc (ts *server) DaemonAddresses() []netip.Addr {\n\taddrs := make([]netip.Addr, 0, 2)\n\tif ts.daemonAddrIPv4.IsValid() {\n\t\taddrs = append(addrs, ts.daemonAddrIPv4)\n\t}\n\tif ts.daemonAddrIPv6.IsValid() {\n\t\taddrs = append(addrs, ts.daemonAddrIPv6)\n\t}\n\treturn addrs\n}\n\nfunc (ts *server) Connect(cr *rpc.ConnectRequest, connectServer grpc.ServerStreamingServer[rpc.Info]) error {\n\tgws := make([]netip.Prefix, len(cr.Gateways))\n\tfor i, gw := range cr.Gateways {\n\t\terr := gws[i].UnmarshalBinary(gw)\n\t\tif err != nil {\n\t\t\treturn status.Error(codes.InvalidArgument, err.Error())\n\t\t}\n\t}\n\tts.pluginPid = int(cr.Pid)\n\tts.gateways = gws\n\terr := connectServer.Send(&rpc.Info{Info: map[string]string{\"name\": client.ProcessName(), \"version\": version.Structured.String()}})\n\tif err != nil {\n\t\treturn status.Error(codes.Aborted, \"failed to send initial response\")\n\t}\n\n\t// The termination of this stream tells the network plugin that the daemon is shutting down.\n\tselect {\n\tcase <-ts.done:\n\tcase <-connectServer.Context().Done():\n\t}\n\treturn nil\n}\n\nfunc (ts *server) CreateEndpoint(ctx context.Context, request *rpc.CreateEndpointRequest) (*emptypb.Empty, error) {\n\terr := ts.createEndpoint(ctx, request)\n\tif err != nil && status.Code(err) == codes.Unknown {\n\t\terr = status.Error(codes.Internal, err.Error())\n\t}\n\treturn &emptypb.Empty{}, err\n}\n\nfunc (ts *server) Join(ctx context.Context, request *rpc.EndpointIdentifier) (*rpc.JoinResponse, error) {\n\trsp, err := ts.join(ctx, request)\n\tif err != nil && status.Code(err) == codes.Unknown {\n\t\terr = status.Error(codes.Internal, err.Error())\n\t}\n\treturn rsp, err\n}\n\n// createAddressEndpoint creates a veth pair with one foot in the daemon's namespace and the other in the network plugin's namespace.\n// The latter is what Docker then moves in to a joining container's namespace and then returns when the container is leaving.\n// The fact that Docker returns this interface makes it possible for us to reuse the veth pair, so we cache those pairs here\n// by the IP of the joining container (Docker frequently reuses those IPs).\nfunc (ts *server) createAddressEndpoint(ctx context.Context) ([2]netlink.Link, error) {\n\tpair, err := createVethPair(ctx)\n\tif err != nil {\n\t\treturn pair, err\n\t}\n\tbr, err := ts.bridge()\n\tif err != nil {\n\t\treturn pair, err\n\t}\n\tvhn := pair[0].Attrs().Name\n\tbrn := br.Attrs().Name\n\tclog.Debugf(ctx, \"link set %s master %s\", vhn, brn)\n\terr = netlink.LinkSetMaster(pair[0], br)\n\tif err != nil {\n\t\treturn pair, fmt.Errorf(\"link set %s master %s failed: %w\", vhn, brn, err)\n\t}\n\tvcn := pair[1].Attrs().Name\n\tclog.Debugf(ctx, \"link set %s netns %d\", vcn, ts.pluginPid)\n\terr = netlink.LinkSetNsPid(pair[1], ts.pluginPid)\n\tif err != nil {\n\t\treturn pair, fmt.Errorf(\"link set %s netns %d failed: %w\", vcn, ts.pluginPid, err)\n\t}\n\treturn pair, err\n}\n\nfunc addrFromRaw(raw []byte) (netip.Addr, error) {\n\tif len(raw) == 0 {\n\t\treturn netip.Addr{}, nil\n\t}\n\tvar ip netip.Addr\n\tif err := ip.UnmarshalBinary(raw); err != nil {\n\t\treturn netip.Addr{}, err\n\t}\n\treturn ip, nil\n}\n\nfunc (ts *server) createEndpoint(ctx context.Context, request *rpc.CreateEndpointRequest) error {\n\tkeyAddr := netip.Addr{}\n\taddrIPv4, err := addrFromRaw(request.AddrIpv4)\n\tif err != nil {\n\t\treturn status.Error(codes.InvalidArgument, err.Error())\n\t}\n\taddrIPv6, err := addrFromRaw(request.AddrIpv6)\n\tif err != nil {\n\t\treturn status.Error(codes.InvalidArgument, err.Error())\n\t}\n\tif request.Daemon {\n\t\tts.daemonAddrIPv4 = addrIPv4\n\t\tts.daemonAddrIPv6 = addrIPv6\n\t}\n\tswitch {\n\tcase addrIPv4.IsValid():\n\t\tkeyAddr = addrIPv4\n\tcase addrIPv6.IsValid():\n\t\tkeyAddr = addrIPv6\n\tdefault:\n\t\treturn status.Error(codes.InvalidArgument, \"neither IPv4 nor IPv6 address provided\")\n\t}\n\t_, loaded := ts.endpoints.LoadOrCompute(request.Id, func() (ep endpoint, cancel bool) {\n\t\tpair, _ := ts.endpointCache.LoadOrCompute(keyAddr, func() (pair [2]netlink.Link, cancel bool) {\n\t\t\tpair, err = ts.createAddressEndpoint(ctx)\n\t\t\treturn pair, err != nil\n\t\t})\n\t\tif err == nil {\n\t\t\tep.vethHost = pair[0]\n\t\t\tep.macAddr = pair[1].Attrs().HardwareAddr\n\t\t\tep.vethCont = pair[1]\n\t\t\tep.daemon = request.Daemon\n\t\t}\n\t\treturn ep, false\n\t})\n\tif loaded {\n\t\treturn status.Error(codes.AlreadyExists, fmt.Sprintf(\"endpoint %s already exists\", request.Id))\n\t}\n\treturn err\n}\n\ntype responseStringer struct {\n\t*rpc.JoinResponse\n}\n\nfunc writeRawIP(raw []byte, w *strings.Builder) {\n\tif len(raw) == 0 {\n\t\tw.WriteString(\"nil\")\n\t\treturn\n\t}\n\tvar ip netip.Addr\n\tif err := ip.UnmarshalBinary(raw); err == nil {\n\t\tw.WriteString(ip.String())\n\t} else {\n\t\t_, _ = fmt.Fprintf(w, \"(%#v: error %v)\", raw, err)\n\t}\n}\n\nfunc writeRawPrefix(raw []byte, w *strings.Builder) {\n\tif len(raw) == 0 {\n\t\tw.WriteString(\"nil\")\n\t\treturn\n\t}\n\tvar pfx netip.Prefix\n\tif err := pfx.UnmarshalBinary(raw); err == nil {\n\t\tw.WriteString(pfx.String())\n\t} else {\n\t\t_, _ = fmt.Fprintf(w, \"(%#v: error %v)\", raw, err)\n\t}\n}\n\nfunc (r responseStringer) String() string {\n\tbld := &strings.Builder{}\n\tbld.WriteString(\"JoinResponse{InterfaceSrcName: \")\n\tbld.WriteString(r.InterfaceSrcName)\n\tbld.WriteString(\", InterfaceDstPrefix: \")\n\tbld.WriteString(r.InterfaceDstPrefix)\n\tbld.WriteString(\", GwIpV4: \")\n\twriteRawPrefix(r.GwIpV4, bld)\n\tbld.WriteString(\", GwIpV6: \")\n\twriteRawPrefix(r.GwIpV6, bld)\n\tbld.WriteString(\", routes: [\")\n\tfor i, r := range r.Routes {\n\t\tif i > 0 {\n\t\t\tbld.WriteString(\", \")\n\t\t}\n\t\twriteRawPrefix(r, bld)\n\t}\n\tbld.WriteString(\"], via: \")\n\twriteRawIP(r.Via, bld)\n\tbld.WriteString(\"}\")\n\treturn bld.String()\n}\n\nfunc (ts *server) isIPv6() bool {\n\tts.watchersMutex.Lock()\n\trs := ts.currentRoutes\n\tts.watchersMutex.Unlock()\n\tfor _, r := range rs {\n\t\tif r.Addr().Is6() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (ts *server) join(ctx context.Context, request *rpc.EndpointIdentifier) (*rpc.JoinResponse, error) {\n\tep, loaded := ts.endpoints.Load(request.Id)\n\tif !loaded {\n\t\treturn nil, status.Error(codes.NotFound, fmt.Sprintf(\"endpoint %s not found\", request.Id))\n\t}\n\tvhn := ep.vethHost.Attrs().Name\n\tclog.Debugf(ctx, \"link set %s up\", vhn)\n\terr := netlink.LinkSetUp(ep.vethHost)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"link set %s up failed: %w\", vhn, err)\n\t}\n\trsp := &rpc.JoinResponse{\n\t\tInterfaceSrcName:   ep.vethCont.Attrs().Name,\n\t\tInterfaceDstPrefix: \"tpd-\",\n\t}\n\tif !ep.daemon {\n\t\tts.watchersMutex.Lock()\n\t\trs := ts.currentRoutes\n\t\tts.watchersMutex.Unlock()\n\t\trsb := make([][]byte, len(rs))\n\t\tfor i, r := range rs {\n\t\t\trsb[i], err = r.MarshalBinary()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\trsp.Routes = rsb\n\t\tif ts.isIPv6() {\n\t\t\tif !ts.daemonAddrIPv6.IsValid() {\n\t\t\t\treturn nil, status.Error(codes.Internal, \"IPv6 is not enabled for the teleroute network\")\n\t\t\t}\n\t\t\trsp.Via, _ = ts.daemonAddrIPv6.MarshalBinary()\n\t\t} else {\n\t\t\tif !ts.daemonAddrIPv4.IsValid() {\n\t\t\t\treturn nil, status.Error(codes.Internal, \"IPv4 is not enabled for the teleroute network\")\n\t\t\t}\n\t\t\trsp.Via, _ = ts.daemonAddrIPv4.MarshalBinary()\n\t\t}\n\t}\n\tfor _, gw := range ts.gateways {\n\t\tif rsp.GwIpV4 == nil && gw.Addr().Is4() {\n\t\t\trsp.GwIpV4, _ = gw.MarshalBinary()\n\t\t}\n\t\tif rsp.GwIpV6 == nil && gw.Addr().Is6() {\n\t\t\trsp.GwIpV6, _ = gw.MarshalBinary()\n\t\t}\n\t}\n\tclog.Debug(ctx, responseStringer{JoinResponse: rsp})\n\treturn rsp, nil\n}\n\nfunc (ts *server) Leave(_ context.Context, request *rpc.EndpointIdentifier) (*emptypb.Empty, error) {\n\t_, loaded := ts.endpoints.Load(request.Id)\n\tif !loaded {\n\t\treturn nil, status.Error(codes.NotFound, fmt.Sprintf(\"endpoint %s not found\", request.Id))\n\t}\n\treturn nil, nil\n}\n\nfunc (ts *server) RemoveEndpoint(_ context.Context, request *rpc.EndpointIdentifier) (*emptypb.Empty, error) {\n\t_, loaded := ts.endpoints.LoadAndDelete(request.Id)\n\tif !loaded {\n\t\treturn nil, status.Error(codes.NotFound, fmt.Sprintf(\"endpoint %s not found\", request.Id))\n\t}\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (ts *server) bridge() (netlink.Link, error) {\n\tbr, err := netlink.LinkByIndex(ts.bridgeIdx)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"could not find bridge %q: %w\", bridgeName, err)\n\t}\n\treturn br, err\n}\n\nfunc (ts *server) serve(ctx context.Context) error {\n\tclog.Infof(ctx, \"Starting service on port %d\", ts.port)\n\tdefer clog.Info(ctx, \"Service stopped\")\n\n\tlc := net.ListenConfig{}\n\ttrListener, err := lc.Listen(ctx, \"tcp\", fmt.Sprintf(\":%d\", ts.port))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar br netlink.Link = &netlink.Bridge{\n\t\tLinkAttrs: netlink.LinkAttrs{\n\t\t\tName: bridgeName,\n\t\t},\n\t}\n\tclog.Debugf(ctx, \"link add %s\", bridgeName)\n\terr = netlink.LinkAdd(br)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"link add %s failed: %w\", bridgeName, err)\n\t}\n\tclog.Debugf(ctx, \"link set %s up\", bridgeName)\n\terr = netlink.LinkSetUp(br)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"link set %s up failed: %w\", bridgeName, err)\n\t}\n\tts.bridgeIdx = br.Attrs().Index\n\n\tgo func() {\n\t\tfor rs := range ts.routesCh {\n\t\t\tts.watchersMutex.Lock()\n\t\t\tts.currentRoutes = rs\n\t\t\tts.watchersMutex.Unlock()\n\t\t}\n\t}()\n\n\t// The Connect stream listener needs to know when it's time to leave, so that\n\t// the grpcServer can perform a graceful shutdown.\n\tts.done = ctx.Done()\n\n\tsvc := grpcServer.New(ctx)\n\trpc.RegisterTelerouteServer(svc, ts)\n\treturn grpcServer.Serve(ctx, svc, trListener)\n}\n\n// nameRndSize is the length of the random byte slice. It's chosen to fit the\n// base32 encoding where each char holds 5 bits.\n// Using the formula (srcSize * 8 + 4) / 5, we then get a 12 char string.\nconst nameRndSize = 7\n\nvar smallcapsEncoding = base32.NewEncoding(\"abcdefghijklmnopqrstuvwxyz234567\").WithPadding(base32.NoPadding) //nolint:gochecknoglobals // constant\n\n// generateInterfaceName creates a random 15-character long name consisting of\n// the prefix \"tp-\" and a 12-character long base32 encoded 7-byte random value.\nfunc generateInterfaceName() string {\n\tn1 := make([]byte, nameRndSize)\n\t_, _ = rand.Read(n1)\n\treturn \"tp-\" + smallcapsEncoding.EncodeToString(n1)\n}\n\nfunc createVethPair(ctx context.Context) (pair [2]netlink.Link, err error) {\n\tveth := &netlink.Veth{\n\t\tLinkAttrs: netlink.LinkAttrs{\n\t\t\tName:         generateInterfaceName(),\n\t\t\tHardwareAddr: vif.RandomMAC(),\n\t\t},\n\t\tPeerName:         generateInterfaceName(),\n\t\tPeerHardwareAddr: vif.RandomMAC(),\n\t}\n\n\tclog.Debugf(ctx, \"link add veth %s/%s\", veth.Attrs().Name, veth.PeerName)\n\terr = netlink.LinkAdd(veth)\n\tif err != nil {\n\t\treturn pair, fmt.Errorf(\"cannot create veth pair %s/%s: %w\", veth.Name, veth.PeerName, err)\n\t}\n\n\tpair[0], err = netlink.LinkByName(veth.Name)\n\tif err != nil {\n\t\treturn pair, linkNotFoundError{name: veth.Name, err: err}\n\t}\n\tpair[1], err = netlink.LinkByName(veth.PeerName)\n\tif err != nil {\n\t\treturn pair, linkNotFoundError{name: veth.PeerName, err: err}\n\t}\n\treturn pair, nil\n}\n"
  },
  {
    "path": "pkg/client/docker/teleroute/server_other.go",
    "content": "//go:build !linux\n\npackage teleroute\n\nimport (\n\t\"net/netip\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\n// StartServer returns nil because the teleroute server is only relevant in a container,\n// and hence, only relevant in linux.\nfunc StartServer(g log.Group, tap *vif.TunnelingDevice, routesCh <-chan []netip.Prefix, teleroutePort uint16) (Server, error) {\n\tfor range routesCh {\n\t}\n\treturn nil, nil\n}\n"
  },
  {
    "path": "pkg/client/docker/volume.go",
    "content": "package docker\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/docker/docker/api/types/volume\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/output\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// CreateVolumes creates the volumes necessary when mounting volumes required by when engaging the remote container.\n// The hostPort is the <daemon ip>/<sftp port> where the access to the remote sftp-server is provided.\n// The mounts are provided as a map of mount policies keyed by paths.\n// Each volume is given the name of the remote container suffixed by a dash and a sequence number, starting at 0.\n// Returns a map of paths keyed by volume names.\nfunc CreateVolumes(\n\tctx context.Context,\n\thostPort netip.AddrPort,\n\tremoteContainer string,\n\tmounts types.MountPolicies,\n\tro bool,\n) (map[string]string, error) {\n\tvar plugin string\n\tvols := make(map[string]string)\n\ti := 0\n\tfor dir, policy := range mounts {\n\t\tvolRO := ro\n\t\tswitch policy {\n\t\tcase types.MountPolicyIgnore:\n\t\t\tcontinue\n\t\tcase types.MountPolicyLocal:\n\t\t\t// Mount using a local binding, unless user already provided a mount.\n\t\tcase types.MountPolicyRemoteReadOnly:\n\t\t\tvolRO = true\n\t\t\tfallthrough\n\t\tcase types.MountPolicyRemote:\n\t\t\tvar err error\n\t\t\tif plugin == \"\" {\n\t\t\t\tplugin, err = EnsureVolumePlugin(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\tioutil.Printf(output.Err(ctx), \"Remote mount disabled: %s\\n\", err)\n\t\t\t\t\treturn nil, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tv := fmt.Sprintf(\"%s-%d\", remoteContainer, i)\n\t\t\ti++\n\t\t\tif err = createVolume(ctx, plugin, hostPort, v, remoteContainer, dir, volRO); err != nil {\n\t\t\t\treturn vols, err\n\t\t\t}\n\t\t\tvols[v] = dir\n\t\t}\n\t}\n\treturn vols, nil\n}\n\nfunc RemoveVolumes(ctx context.Context, vols []string) {\n\tfor _, vol := range vols {\n\t\tif err := removeVolume(ctx, vol); err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t}\n\t}\n}\n\nfunc VolumeDriverOpts(ctx context.Context, pluginName string, hostPort netip.AddrPort, volumeName, container, dir string, ro bool) map[string]string {\n\topts := map[string]string{\n\t\t\"host\":      hostPort.Addr().String(),\n\t\t\"container\": container,\n\t\t\"port\":      strconv.Itoa(int(hostPort.Port())),\n\t\t\"dir\":       dir,\n\t}\n\tif ro {\n\t\tver := parsePluginSemver(pluginName)\n\t\tif ver != nil && ver.LT(semver.MustParse(\"0.1.6\")) {\n\t\t\tclog.Warnf(ctx, \"The %q docker volume plugin does not support read-only mode. Please upgrade to a more recent version\", pluginName)\n\t\t} else {\n\t\t\topts[\"ro\"] = \"true\"\n\t\t}\n\t}\n\treturn opts\n}\n\n// parsePluginSemver extracts a semantic version from a plugin name formatted like \"name-<semver>\".\nfunc parsePluginSemver(name string) *semver.Version {\n\tif i := strings.LastIndexByte(name, '-'); i > 0 {\n\t\ttag := name[i+1:]\n\t\tif v, err := semver.Parse(tag); err == nil {\n\t\t\treturn &v\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc createVolume(ctx context.Context, pluginName string, hostPort netip.AddrPort, volumeName, container, dir string, ro bool) error {\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\topts := VolumeDriverOpts(ctx, pluginName, hostPort, volumeName, container, dir, ro)\n\n\tclog.Debugf(ctx, \"VolumeCreate(%s, %s, %s)\", pluginName, opts, volumeName)\n\t_, err = cli.VolumeCreate(ctx, volume.CreateOptions{\n\t\tDriver:     pluginName,\n\t\tDriverOpts: opts,\n\t\tName:       volumeName,\n\t})\n\tif err != nil {\n\t\terr = fmt.Errorf(\"docker volume create %s %s %s: %w\", hostPort, container, dir, err)\n\t}\n\treturn err\n}\n\nfunc removeVolume(ctx context.Context, volume string) error {\n\tcli, err := GetClient(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = cli.VolumeRemove(ctx, volume, false)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"docker volume rm %s: %w\", volume, err)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/docker/volume_test.go",
    "content": "package docker\n\nimport (\n\t\"testing\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\nfunc Test_getLatestPluginVersion(t *testing.T) {\n\tc := testutil.NewContext(t, false)\n\tenv, err := client.LoadEnv()\n\trequire.NoError(t, err)\n\tc = client.WithEnv(c, &env)\n\n\tcfg, err := client.LoadConfig(c)\n\trequire.NoError(t, err)\n\tc = client.WithConfig(c, cfg)\n\n\tdi := client.DockerImage(cfg.Docker().Telemount)\n\tver, err := getLatestPluginVersion(c, pluginName(&di), &di)\n\trequire.NoError(t, err)\n\trequire.True(t, ver.EQ(zeroVersion) || semver.MustParse(\"0.1.3\").LT(ver))\n}\n"
  },
  {
    "path": "pkg/client/ensured_state.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n)\n\ntype (\n\tProlog func(context.Context) (acquired bool, err error)\n\tAction func(context.Context) error\n)\n\n// WithEnsuredState calls prolog, and if that was successful, calls act. If epilog is not nil, it is guaranteed\n// to be called when prolog returns true.\nfunc WithEnsuredState(ctx context.Context, prolog Prolog, action, epilog Action) error {\n\twasAcquired, err := prolog(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif wasAcquired && epilog != nil {\n\t\tdefer func() {\n\t\t\t// The context might have been cancelled, so we use the original context\n\t\t\t// without cancellation, but with a deactivation timeout of 10 seconds.\n\t\t\tctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second)\n\t\t\tdefer cancel()\n\t\t\tif cerr := epilog(ctx); cerr != nil {\n\t\t\t\tif err == nil {\n\t\t\t\t\terr = cerr\n\t\t\t\t} else {\n\t\t\t\t\terr = fmt.Errorf(\"%w\\n%v\", err, cerr)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\tif action != nil {\n\t\terr = action(ctx)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/envconfig.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\tenv2 \"github.com/caarlos0/env/v11\"\n)\n\ntype Env struct {\n\tOSSpecificEnv\n\tManagerNamespace string `env:\"TELEPRESENCE_MANAGER_NAMESPACE\" required:\"true\"`\n\n\t// This environment variable becomes the default for the images.registry and images.webhookRegistry\n\tRegistry string `env:\"TELEPRESENCE_REGISTRY\"`\n\n\t// This environment variable becomes the default for the images.agentImage and images.webhookAgentImage\n\tAgentImage string `env:\"TELEPRESENCE_AGENT_IMAGE\"`\n\n\t// This environment variable becomes the default for the images.clientImage\n\tClientImage string `env:\"TELEPRESENCE_CLIENT_IMAGE\"`\n\n\t// The address that the user daemon is listening to (unless it is started by the client and uses a named pipe or unix socket).\n\tUserDaemonAddress string `env:\"TELEPRESENCE_USER_DAEMON_ADDRESS\"`\n}\n\ntype envKey struct{}\n\n// WithEnv returns a context with the given Env.\nfunc WithEnv(ctx context.Context, env *Env) context.Context {\n\treturn context.WithValue(ctx, envKey{}, env)\n}\n\nfunc GetEnv(ctx context.Context) *Env {\n\tenv, ok := ctx.Value(envKey{}).(*Env)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn env\n}\n\nfunc LoadEnv() (Env, error) {\n\treturn env2.ParseAs[Env]()\n}\n\nfunc LoadEnvWith(environment map[string]string) (Env, error) {\n\treturn env2.ParseAsWithOptions[Env](env2.Options{Environment: environment})\n}\n"
  },
  {
    "path": "pkg/client/envconfig_unix.go",
    "content": "//go:build !windows\n\npackage client\n\ntype OSSpecificEnv struct {\n\tShell string `env:\"SHELL\" default:\"/bin/bash\"`\n}\n"
  },
  {
    "path": "pkg/client/envconfig_windows.go",
    "content": "package client\n\ntype OSSpecificEnv struct {\n\tShell string `env:\"ComSpec\" default:\"C:\\\\WINDOWS\\\\system32\\\\cmd.exe\"`\n}\n"
  },
  {
    "path": "pkg/client/install_id.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/google/uuid\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc InstallID(ctx context.Context) (string, error) {\n\tidFile := filepath.Join(filelocation.AppUserConfigDir(ctx), \"id\")\n\tdata, err := dos.ReadFile(ctx, idFile)\n\tswitch {\n\tcase err == nil:\n\t\treturn strings.TrimSpace(string(data)), nil\n\tcase errors.Is(err, fs.ErrNotExist):\n\t\tid := uuid.New().String()\n\t\tif err = dos.WriteFile(ctx, idFile, []byte(id), 0o644); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn id, nil\n\tdefault:\n\t\treturn \"\", err\n\t}\n}\n"
  },
  {
    "path": "pkg/client/k8s/cani.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\n\tv1 \"k8s.io/api/authorization/v1\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\n// CanPortForward answers the question if this client has the RBAC permissions necessary\n// to perform a port-forward to the connected namespace.\nfunc CanPortForward(ctx context.Context, namespace string) bool {\n\tok, err := k8sapi.CanI(ctx, &v1.ResourceAttributes{\n\t\tVerb:        \"create\",\n\t\tResource:    \"pods\",\n\t\tSubresource: \"portforward\",\n\t\tNamespace:   namespace,\n\t})\n\treturn err == nil && ok\n}\n"
  },
  {
    "path": "pkg/client/k8s/cluster.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/cenkalti/backoff/v4\"\n\tauth \"k8s.io/api/authorization/v1\"\n\tcore \"k8s.io/api/core/v1\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/version\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n)\n\nconst (\n\tsupportedKubeAPIVersion = \"1.17.0\"\n\tdefaultManagerNamespace = \"ambassador\"\n)\n\ntype NamespaceListener func()\n\n// Cluster is a Kubernetes cluster reference.\ntype Cluster struct {\n\t*Kubeconfig\n\tMappedNamespaces []string\n\n\t// nsLock protects namespaceWatcherSnapshot, currentMappedNamespaces and namespaceEventHandlers\n\tnsLock sync.Mutex\n\n\t// snapshot maintained by the namespaces watcher.\n\tnamespaceWatcherSnapshot map[string]struct{}\n\n\t// Current Namespace snapshot, filtered by MappedNamespaces\n\tcurrentMappedNamespaces map[string]bool\n\n\t// Namespace listener. Notified when the currentNamespaces changes\n\tnamespaceEventHandlers []NamespaceListener\n}\n\nfunc (kc *Cluster) ActualNamespace(namespace string) string {\n\tif namespace == \"\" {\n\t\tnamespace = kc.Namespace\n\t}\n\tif !kc.namespaceAccessible(namespace) {\n\t\tnamespace = \"\"\n\t}\n\treturn namespace\n}\n\n// check uses a non-caching DiscoveryClientConfig to retrieve the server version.\nfunc (kc *Cluster) check(c context.Context) error {\n\t// The discover client is using context.TODO() so the timeout specified in our\n\t// context has no effect.\n\tvar info *version.Info\n\tdsc := k8sapi.GetK8sInterface(kc).Discovery()\n\terr := backoff.Retry(func() (err error) {\n\t\tif info, err = dsc.ServerVersion(); err != nil {\n\t\t\tif !strings.Contains(err.Error(), \"connection refused\") {\n\t\t\t\terr = backoff.Permanent(err)\n\t\t\t}\n\t\t}\n\t\treturn err\n\t}, backoff.WithContext(backoff.WithMaxRetries(backoff.NewConstantBackOff(400*time.Millisecond), 4), c))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"initial cluster check failed: %w\", client.RunError(err))\n\t}\n\t// Validate that the kubernetes server version is supported\n\tclog.Infof(c, \"Server version %s\", info.GitVersion)\n\tgitVer, err := semver.Parse(strings.TrimPrefix(info.GitVersion, \"v\"))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error converting version %s to semver: %s\", info.GitVersion, err)\n\t}\n\tsupGitVer, err := semver.Parse(supportedKubeAPIVersion)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error converting known version %s to semver: %s\", supportedKubeAPIVersion, err)\n\t}\n\tif gitVer.LT(supGitVer) {\n\t\treturn fmt.Errorf(\"kubernetes server versions older than %s are not supported, using %s\", supportedKubeAPIVersion, info.GitVersion)\n\t}\n\treturn nil\n}\n\nfunc (kc *Cluster) CheckTrafficManagerService(ctx context.Context, namespace string) error {\n\tclog.Debug(ctx, \"checking that traffic-manager exists\")\n\tcoreV1 := k8sapi.GetK8sInterface(kc).CoreV1()\n\tif _, err := coreV1.Services(namespace).Get(ctx, agentconfig.ManagerAppName, meta.GetOptions{}); err != nil {\n\t\tmsg := fmt.Sprintf(\"unable to get service %s in %s: %v\", agentconfig.ManagerAppName, namespace, err)\n\t\tse := &k8serrors.StatusError{}\n\t\tif errors.As(err, &se) {\n\t\t\tif se.Status().Code == http.StatusNotFound {\n\t\t\t\tclog.Error(ctx, msg)\n\t\t\t\tmsg = \"traffic manager not found, if it is not installed, please run 'telepresence helm install'. \" +\n\t\t\t\t\t\"If it is installed, try connecting with a --manager-namespace to point telepresence to the namespace it's installed in.\"\n\t\t\t}\n\t\t}\n\t\treturn errcat.User.New(msg)\n\t}\n\treturn nil\n}\n\n// namespaceAccessible answers the question if the namespace is present and accessible\n// to this client.\nfunc (kc *Cluster) namespaceAccessible(namespace string) (exists bool) {\n\tkc.nsLock.Lock()\n\tok := kc.currentMappedNamespaces[namespace]\n\tkc.nsLock.Unlock()\n\treturn ok\n}\n\nfunc NewCluster(kubeFlags *Kubeconfig, namespaces []string) (*Cluster, error) {\n\tret := &Cluster{Kubeconfig: kubeFlags}\n\n\tcfg := client.GetConfig(ret)\n\ttimedC, cancel := cfg.Timeouts().TimeoutContext(kubeFlags, client.TimeoutClusterConnect)\n\tdefer cancel()\n\tif err := ret.check(timedC); err != nil {\n\t\treturn nil, err\n\t}\n\n\tclog.Infof(ret, \"Context: %s\", ret.KubeContext)\n\tclog.Infof(ret, \"Server: %s\", ret.Server)\n\n\tif len(namespaces) == 1 && namespaces[0] == \"all\" {\n\t\tnamespaces = nil\n\t}\n\tif len(namespaces) == 0 {\n\t\tnamespaces = cfg.Cluster().MappedNamespaces\n\t}\n\tif len(namespaces) == 0 {\n\t\tif k8sapi.CanWatchNamespaces(ret) {\n\t\t\tclog.Infof(ret, \"Will watch all namespaces\")\n\t\t\tret.StartNamespaceWatcher()\n\t\t} else {\n\t\t\tclog.Warnf(ret, \"Unable to watch all namespaces\")\n\t\t}\n\t} else {\n\t\tclog.Infof(ret, \"Will use mapped namespaces %s\", namespaces)\n\t\tret.SetMappedNamespaces(namespaces)\n\t}\n\tif GetManagerNamespace(ret) == \"\" {\n\t\ttns, err := ret.determineTrafficManagerNamespace()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcfg.Cluster().DefaultManagerNamespace = tns\n\t}\n\tclog.Infof(ret, \"Will look for traffic manager in namespace %s\", GetManagerNamespace(ret))\n\treturn ret, nil\n}\n\nfunc parseCIDR(cidr []string) ([]netip.Prefix, error) {\n\tif len(cidr) == 0 {\n\t\treturn nil, nil\n\t}\n\tresult := make([]netip.Prefix, len(cidr))\n\tfor i := range cidr {\n\t\tipNet, err := netip.ParsePrefix(cidr[i])\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse CIDR %s: %w\", cidr[i], err)\n\t\t}\n\t\tresult[i] = ipNet\n\t}\n\treturn result, nil\n}\n\nfunc ConnectCluster(cr *rpc.ConnectRequest, config *Kubeconfig) (*Cluster, error) {\n\tmappedNamespaces := cr.MappedNamespaces\n\tif len(mappedNamespaces) == 1 && mappedNamespaces[0] == \"all\" {\n\t\tmappedNamespaces = nil\n\t} else {\n\t\tsort.Strings(mappedNamespaces)\n\t}\n\n\tcluster, err := NewCluster(config, mappedNamespaces)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\textraAlsoProxy, err := parseCIDR(cr.GetAlsoProxy())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse extra also proxy: %w\", err)\n\t}\n\n\textraNeverProxy, err := parseCIDR(cr.GetNeverProxy())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse extra never proxy: %w\", err)\n\t}\n\n\textraAllow, err := parseCIDR(cr.GetAllowConflictingSubnets())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse extra allow conflicting subnets: %w\", err)\n\t}\n\tif len(extraAlsoProxy)+len(extraNeverProxy)+len(extraAllow) > 0 {\n\t\tcfg := client.GetConfig(cluster)\n\t\trt := cfg.Routing()\n\t\trt.AllowConflicting = append(rt.AllowConflicting, extraAllow...)\n\t\trt.AlsoProxy = append(rt.AlsoProxy, extraAlsoProxy...)\n\t\trt.NeverProxy = append(rt.NeverProxy, extraNeverProxy...)\n\t\tclient.ReplaceConfig(cluster, cfg)\n\t}\n\n\treturn cluster, nil\n}\n\n// determineTrafficManagerNamespace finds the namespace for the traffic-manager. It is determined by the following steps:\n//\n//  1. If a traffic-manager service is found in one of the currently accessible namespaces, return it.\n//  2. If the client has access to the default manager namespace, then return it.\n//  3. If the client has access to the default namespace, then return it.\n//  4. Return an error stating that it isn't possible to determine the namespace.\nfunc (kc *Cluster) determineTrafficManagerNamespace() (string, error) {\n\t// Search for the traffic-manager in mapped namespaces\n\tnss := kc.GetCurrentNamespaces(true)\n\tfor _, ns := range nss {\n\t\tif _, err := k8sapi.GetService(kc, agentconfig.ManagerAppName, ns); err == nil {\n\t\t\treturn ns, nil\n\t\t}\n\t}\n\n\t// No existing manager was found.\n\tif canGetDefaultTrafficManagerService(kc) {\n\t\treturn defaultManagerNamespace, nil\n\t}\n\n\t// No existing traffic-manager found. Assume that it should be installed\n\t// in the default namespace if it is accessible\n\tif canAccessNS(kc, kc.Namespace) {\n\t\treturn kc.Namespace, nil\n\t}\n\treturn \"\", errcat.User.New(\"unable to determine the traffic-manager namespace\")\n}\n\n// GetCurrentNamespaces returns the names of the namespaces that this client\n// is mapping. If the forClientAccess is true, then the namespaces are restricted\n// to those where an intercept can take place, i.e. the namespaces where this\n// client can get pods.\nfunc (kc *Cluster) GetCurrentNamespaces(forClientAccess bool) []string {\n\tkc.nsLock.Lock()\n\tnss := make([]string, 0, len(kc.currentMappedNamespaces))\n\tif forClientAccess {\n\t\tfor ns, ok := range kc.currentMappedNamespaces {\n\t\t\tif ok {\n\t\t\t\tnss = append(nss, ns)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor ns := range kc.currentMappedNamespaces {\n\t\t\tnss = append(nss, ns)\n\t\t}\n\t}\n\tkc.nsLock.Unlock()\n\tsort.Strings(nss)\n\treturn nss\n}\n\nfunc (kc *Cluster) GetManagerInstallId() string {\n\tmanagerID, _ := k8sapi.GetNamespaceID(kc, GetManagerNamespace(kc))\n\treturn managerID\n}\n\nfunc GetManagerNamespace(ctx context.Context) string {\n\treturn client.GetConfig(ctx).Cluster().DefaultManagerNamespace\n}\n\n// canGetDefaultTrafficManagerService answers the question if this client has the RBAC permissions\n// necessary to get the traffic-manager in the default namespace.\nfunc canGetDefaultTrafficManagerService(ctx context.Context) bool {\n\tok, err := k8sapi.CanI(ctx, &auth.ResourceAttributes{\n\t\tVerb:      \"get\",\n\t\tResource:  \"services\",\n\t\tName:      agentconfig.ManagerAppName,\n\t\tNamespace: defaultManagerNamespace,\n\t})\n\treturn err == nil && ok\n}\n\n// canAccessNS answers the question if this client has the RBAC permissions\n// necessary to get a pod in the given namespace.\nfunc canAccessNS(ctx context.Context, namespace string) bool {\n\tok, err := k8sapi.CanI(ctx, &auth.ResourceAttributes{\n\t\tNamespace: namespace,\n\t\tVerb:      \"get\",\n\t\tResource:  \"pods\",\n\t\tGroup:     \"\",\n\t})\n\treturn err == nil && ok\n}\n\n// StartNamespaceWatcher runs a Kubernetes Watcher that provide information about the cluster's namespaces'.\n// The function waits for the first snapshot to arrive before returning.\nfunc (kc *Cluster) StartNamespaceWatcher() {\n\tkc.namespaceWatcherSnapshot = make(map[string]struct{})\n\tnsSynced := make(chan struct{})\n\tcloseSynced := sync.Once{}\n\tgo func() {\n\t\tapi := k8sapi.GetK8sInterface(kc).CoreV1()\n\t\tfor kc.Err() == nil {\n\t\t\tw, err := api.Namespaces().Watch(kc, meta.ListOptions{})\n\t\t\tif err != nil {\n\t\t\t\tclog.Errorf(kc, \"unable to create service watcher: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tkc.namespacesEventHandler(w.ResultChan(), nsSynced, &closeSynced)\n\t\t}\n\t}()\n\tselect {\n\tcase <-kc.Done():\n\tcase <-nsSynced:\n\t}\n}\n\nfunc (kc *Cluster) namespacesEventHandler(evCh <-chan watch.Event, nsSynced chan struct{}, closeSynced *sync.Once) {\n\t// The delay timer will initially sleep forever. It's reset to a very short\n\t// delay when the file is modified.\n\tdefer func() {\n\t\tcloseSynced.Do(func() { close(nsSynced) })\n\t}()\n\tdelay := time.AfterFunc(time.Duration(math.MaxInt64), func() {\n\t\tkc.refreshNamespaces()\n\t\tcloseSynced.Do(func() { close(nsSynced) })\n\t})\n\tdefer delay.Stop()\n\n\tfor {\n\t\tselect {\n\t\tcase <-kc.Done():\n\t\t\treturn\n\t\tcase event, ok := <-evCh:\n\t\t\tif !ok {\n\t\t\t\treturn // restart watcher\n\t\t\t}\n\t\t\tns, ok := event.Object.(*core.Namespace)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkc.nsLock.Lock()\n\t\t\tswitch event.Type {\n\t\t\tcase watch.Deleted:\n\t\t\t\tdelete(kc.namespaceWatcherSnapshot, ns.Name)\n\t\t\tcase watch.Added, watch.Modified:\n\t\t\t\tkc.namespaceWatcherSnapshot[ns.Name] = struct{}{}\n\t\t\t}\n\t\t\tkc.nsLock.Unlock()\n\n\t\t\t// We consider the watcher synced after 10 ms of inactivity. It's not a big deal\n\t\t\t// if more namespaces arrive after that.\n\t\t\tdelay.Reset(10 * time.Millisecond)\n\t\t}\n\t}\n}\n\nfunc (kc *Cluster) SetMappedNamespaces(namespaces []string) bool {\n\tsort.Strings(namespaces)\n\tif !slices.Equal(namespaces, kc.MappedNamespaces) {\n\t\tkc.MappedNamespaces = namespaces\n\t\tkc.refreshNamespaces()\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (kc *Cluster) AddNamespaceEventHandler(nsEventHandler NamespaceListener) {\n\tkc.nsLock.Lock()\n\tkc.namespaceEventHandlers = append(kc.namespaceEventHandlers, nsEventHandler)\n\tkc.nsLock.Unlock()\n\tnsEventHandler()\n}\n\nfunc (kc *Cluster) refreshNamespaces() {\n\tkc.nsLock.Lock()\n\tvar nss []string\n\tif kc.namespaceWatcherSnapshot == nil {\n\t\t// No permission to watch namespaces. Use the mapped-namespaces instead.\n\t\tnss = kc.MappedNamespaces\n\t\tif len(nss) == 0 {\n\t\t\t// No mapped namespaces exists. Fallback to what's defined in the kube-context (will be \"default\" if none was defined).\n\t\t\tnss = []string{kc.Namespace}\n\t\t}\n\t} else {\n\t\tnss = make([]string, len(kc.namespaceWatcherSnapshot))\n\t\ti := 0\n\t\tfor ns := range kc.namespaceWatcherSnapshot {\n\t\t\tnss[i] = ns\n\t\t\ti++\n\t\t}\n\t}\n\tnamespaces := make(map[string]bool, len(nss))\n\tfor _, ns := range nss {\n\t\tif kc.shouldBeWatched(ns) {\n\t\t\taccessOk, ok := kc.currentMappedNamespaces[ns]\n\t\t\tif !ok {\n\t\t\t\taccessOk = canAccessNS(kc, ns)\n\t\t\t}\n\t\t\tnamespaces[ns] = accessOk\n\t\t}\n\t}\n\tif maps.Equal(namespaces, kc.currentMappedNamespaces) {\n\t\tkc.nsLock.Unlock()\n\t} else {\n\t\tclog.Debugf(kc, \"Namespaces changed: %v\", namespaces)\n\t\tkc.currentMappedNamespaces = namespaces\n\t\tnsListeners := slices.Clone(kc.namespaceEventHandlers)\n\t\tkc.nsLock.Unlock()\n\t\tfor _, nsListener := range nsListeners {\n\t\t\tnsListener()\n\t\t}\n\t}\n}\n\nfunc (kc *Cluster) shouldBeWatched(namespace string) bool {\n\tif len(kc.MappedNamespaces) == 0 {\n\t\treturn true\n\t}\n\tfor _, n := range kc.MappedNamespaces {\n\t\tif n == namespace {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/client/k8s/config.go",
    "content": "package k8s\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/csv\"\n\t\"net\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/spf13/pflag\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/cli-runtime/pkg/genericclioptions\"\n\t\"k8s.io/client-go/discovery\"\n\t\"k8s.io/client-go/discovery/cached/memory\"\n\t\"k8s.io/client-go/kubernetes\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth\" // Important for various cloud provider auth\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/restmapper\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/client-go/tools/clientcmd/api\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/portforward\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\n// The dnsConfig is part of the kubeconfigExtension struct.\n//\n// Deprecated: Use Config as the Kubeconfig extension.\ntype dnsConfig struct {\n\t// LocalIP is the address of the local DNS server. This entry is only\n\t// used on Linux system that are not configured to use systemd-resolved and\n\t// can be overridden by using the option --dns on the command line and defaults\n\t// to the first line of /etc/resolv.conf\n\tLocalIP netip.Addr `json:\"local-ip,omitempty\"`\n\n\t// RemoteIP is the address of the cluster's DNS service. It will default\n\t// to the IP of the kube-dns.kube-system or the dns-default.openshift-dns service.\n\tRemoteIP netip.Addr `json:\"remote-ip,omitempty\"`\n\n\t// ExcludeSuffixes are suffixes for which the DNS resolver will always return\n\t// NXDOMAIN (or fallback in case of the overriding resolver).\n\tExcludeSuffixes []string `json:\"exclude-suffixes,omitempty\"`\n\n\t// IncludeSuffixes are suffixes for which the DNS resolver will always attempt to do\n\t// a lookup. Includes have higher priority than excludes.\n\tIncludeSuffixes []string `json:\"include-suffixes,omitempty\"`\n\n\t// Excludes are a list of hostname that the DNS resolver will not resolve even if they exist.\n\tExcludes []string `json:\"excludes,omitempty\"`\n\n\t// Mappings contains a list of DNS Mappings. Each item references a hostname, and an associated alias. If a\n\t// request is made for the name, the alias will be resolved instead.\n\tMappings client.DNSMappings `json:\"mappings,omitempty\"`\n\n\t// The maximum time to wait for a cluster side host lookup.\n\tLookupTimeout v1.Duration `json:\"lookup-timeout,omitempty\"`\n}\n\n// The managerConfig is part of the kubeconfigExtension struct. It configures discovery of the traffic manager.\n//\n// Deprecated: Use Config as the Kubeconfig extension.\ntype managerConfig struct {\n\t// Namespace is the name of the namespace where the traffic manager is to be found\n\tNamespace string `json:\"namespace,omitempty\"`\n}\n\n// kubeconfigExtension is an extension read from the selected kubeconfig Cluster.\n//\n// Deprecated: Use Config as the Kubeconfig extension.\ntype kubeconfigExtension struct {\n\tDNS                     *dnsConfig     `json:\"dns,omitempty\"`\n\tAlsoProxy               []netip.Prefix `json:\"also-proxy,omitempty\"`\n\tNeverProxy              []netip.Prefix `json:\"never-proxy,omitempty\"`\n\tAllowConflictingSubnets []netip.Prefix `json:\"allow-conflicting-subnets,omitempty\"`\n\tManager                 *managerConfig `json:\"manager,omitempty\"`\n}\n\nfunc (ke *kubeconfigExtension) asConfig() client.Config {\n\tcfg := client.GetDefaultConfig()\n\tif keDns := ke.DNS; keDns != nil {\n\t\tdns := cfg.DNS()\n\t\tif len(keDns.Excludes) > 0 {\n\t\t\tdns.Excludes = keDns.Excludes\n\t\t}\n\t\tif len(keDns.ExcludeSuffixes) > 0 {\n\t\t\tdns.ExcludeSuffixes = keDns.ExcludeSuffixes\n\t\t}\n\t\tif len(keDns.IncludeSuffixes) > 0 {\n\t\t\tdns.IncludeSuffixes = keDns.IncludeSuffixes\n\t\t}\n\t\tif len(keDns.Mappings) > 0 {\n\t\t\tdns.Mappings = keDns.Mappings\n\t\t}\n\t\tif keDns.LocalIP.IsValid() {\n\t\t\tdns.LocalAddresses = []netip.AddrPort{netip.AddrPortFrom(keDns.LocalIP, 53)}\n\t\t}\n\t\tif keDns.RemoteIP.IsValid() {\n\t\t\tdns.VIFAddress = netip.AddrPortFrom(keDns.RemoteIP, 53)\n\t\t}\n\t\tif keDns.LookupTimeout.Duration != 0 {\n\t\t\tdns.LookupTimeout = keDns.LookupTimeout.Duration\n\t\t}\n\t}\n\trt := cfg.Routing()\n\tif len(ke.NeverProxy) > 0 {\n\t\trt.NeverProxy = ke.NeverProxy\n\t}\n\tif len(ke.AlsoProxy) > 0 {\n\t\trt.AlsoProxy = ke.AlsoProxy\n\t}\n\tif len(ke.AllowConflictingSubnets) > 0 {\n\t\trt.AllowConflicting = ke.AllowConflictingSubnets\n\t}\n\tif m := ke.Manager; m != nil && m.Namespace != \"\" {\n\t\tcfg.Cluster().DefaultManagerNamespace = m.Namespace\n\t}\n\treturn cfg\n}\n\n// Kubeconfig implements genericclioptions.RESTClientGetter, but is using the RestConfig\n// instead of the ConfigFlags (which also implements that interface) since the latter\n// will assume that the kubeconfig is loaded from disk.\ntype Kubeconfig struct {\n\tcontext.Context\n\tNamespace        string // default cluster namespace.\n\tKubeContext      string\n\tServer           string\n\tOriginalFlagMap  map[string]string\n\tEffectiveFlagMap map[string]string\n\tClientConfig     clientcmd.ClientConfig\n\tRestConfig       *rest.Config\n}\n\nfunc (kf *Kubeconfig) ToRESTConfig() (*rest.Config, error) {\n\treturn kf.RestConfig, nil\n}\n\nfunc (kf *Kubeconfig) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {\n\tdiscoveryClient, err := discovery.NewDiscoveryClientForConfig(kf.RestConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn memory.NewMemCacheClient(discoveryClient), nil\n}\n\nfunc (kf *Kubeconfig) ToRESTMapper() (meta.RESTMapper, error) {\n\tdiscoveryClient, err := kf.ToDiscoveryClient()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)\n\texpander := restmapper.NewShortcutExpander(mapper, discoveryClient, func(string) {})\n\treturn expander, nil\n}\n\nfunc (kf *Kubeconfig) ToRawKubeConfigLoader() clientcmd.ClientConfig {\n\treturn kf.ClientConfig\n}\n\nconst configExtension = \"telepresence.io\"\n\nfunc ConfigFlags(flagMap map[string]string) (*genericclioptions.ConfigFlags, error) {\n\tconfigFlags := genericclioptions.NewConfigFlags(false)\n\tflags := pflag.NewFlagSet(\"\", 0)\n\tconfigFlags.AddFlags(flags)\n\tfor k, v := range flagMap {\n\t\tf := flags.Lookup(k)\n\t\tif f == nil {\n\t\t\tcontinue\n\t\t}\n\t\tvar err error\n\t\tif sv, ok := f.Value.(pflag.SliceValue); ok {\n\t\t\tvar vs []string\n\t\t\tif vs, err = csv.NewReader(strings.NewReader(v)).Read(); err == nil {\n\t\t\t\terr = sv.Replace(vs)\n\t\t\t}\n\t\t} else {\n\t\t\terr = flags.Set(k, v)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn nil, errcat.User.Errorf(err, \"error processing kubectl flag --%s=%s\", k, v)\n\t\t}\n\t}\n\treturn configFlags, nil\n}\n\n// ConfigLoader returns the name of the current Kubernetes context, and the context itself.\nfunc ConfigLoader(ctx context.Context, flagMap map[string]string, kubeConfigData []byte) (clientcmd.ClientConfig, error) {\n\tconfigFlags, err := ConfigFlags(flagMap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn NewClientConfig(ctx, configFlags, kubeConfigData)\n}\n\nfunc NewKubeconfig(c context.Context, tpClientConfigIsFinal bool, flagMap map[string]string, managerNamespaceOverride string, kubeconfigData []byte) (*Kubeconfig, error) {\n\tconfigFlags, err := ConfigFlags(flagMap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newKubeconfig(c, tpClientConfigIsFinal, flagMap, flagMap, managerNamespaceOverride, configFlags, kubeconfigData)\n}\n\nfunc DaemonKubeconfig(c context.Context, cr *connector.ConnectRequest) (*Kubeconfig, error) {\n\tif cr.IsPodDaemon {\n\t\tke, err := NewInClusterConfig(c, cr.KubeFlags)\n\t\treturn ke, err\n\t}\n\tflagMap := cr.KubeFlags\n\tif proc.RunningInContainer() {\n\t\t// Don't trust the host's KUBECONFIG env.\n\t\tdelete(cr.Environment, \"KUBECONFIG\")\n\t}\n\tfor k, v := range cr.Environment {\n\t\tif k[0] == '-' {\n\t\t\t_ = os.Unsetenv(k[1:])\n\t\t} else {\n\t\t\t_ = os.Setenv(k, v)\n\t\t}\n\t}\n\tconfigFlags, err := ConfigFlags(flagMap)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newKubeconfig(c, false, cr.KubeFlags, flagMap, cr.ManagerNamespace, configFlags, cr.KubeconfigData)\n}\n\n// AppendKubeFlags appends the flags in the given map to the given slice in the form of\n// flag arguments suitable for command execution. Flags known to be multivalued are assumed\n// to be in the form of comma-separated list and will be added using repeated options.\nfunc AppendKubeFlags(kubeFlags map[string]string, args []string) ([]string, error) {\n\tfor k, v := range kubeFlags {\n\t\tswitch k {\n\t\tcase \"as-group\":\n\t\t\t// Multivalued\n\t\t\tr := csv.NewReader(strings.NewReader(v))\n\t\t\tgs, err := r.Read()\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tfor _, g := range gs {\n\t\t\t\targs = append(args, \"--\"+k, g)\n\t\t\t}\n\t\tcase \"disable-compression\", \"insecure-skip-tls-verify\":\n\t\t\t// Boolean with false default.\n\t\t\tif v != \"false\" {\n\t\t\t\targs = append(args, \"--\"+k)\n\t\t\t}\n\t\tdefault:\n\t\t\targs = append(args, \"--\"+k, v)\n\t\t}\n\t}\n\treturn args, nil\n}\n\n// flagOverrides creates overrides based on the given ConfigFlags.\n//\n// The code in this function is copied from clientcmd.config_flags.go, function toRawKubeConfigLoader\n// but differs in that overrides are only made for non-zero values.\nfunc flagOverrides(f *genericclioptions.ConfigFlags) *clientcmd.ConfigOverrides {\n\toverrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults}\n\n\tstringVal := func(vp *string) (v string, ok bool) {\n\t\tif vp != nil && *vp != \"\" {\n\t\t\tv, ok = *vp, true\n\t\t}\n\t\treturn v, ok\n\t}\n\n\t// bind auth info flag values to overrides\n\tif v, ok := stringVal(f.CertFile); ok {\n\t\toverrides.AuthInfo.ClientCertificate = v\n\t}\n\tif v, ok := stringVal(f.KeyFile); ok {\n\t\toverrides.AuthInfo.ClientKey = v\n\t}\n\tif v, ok := stringVal(f.BearerToken); ok {\n\t\toverrides.AuthInfo.Token = v\n\t}\n\tif v, ok := stringVal(f.Impersonate); ok {\n\t\toverrides.AuthInfo.Impersonate = v\n\t}\n\tif v, ok := stringVal(f.ImpersonateUID); ok {\n\t\toverrides.AuthInfo.ImpersonateUID = v\n\t}\n\tif f.ImpersonateGroup != nil && len(*f.ImpersonateGroup) > 0 {\n\t\toverrides.AuthInfo.ImpersonateGroups = *f.ImpersonateGroup\n\t}\n\tif v, ok := stringVal(f.Username); ok {\n\t\toverrides.AuthInfo.Username = v\n\t}\n\tif v, ok := stringVal(f.Password); ok {\n\t\toverrides.AuthInfo.Password = v\n\t}\n\n\t// bind cluster flags\n\tif v, ok := stringVal(f.APIServer); ok {\n\t\toverrides.ClusterInfo.Server = v\n\t}\n\tif v, ok := stringVal(f.TLSServerName); ok {\n\t\toverrides.ClusterInfo.TLSServerName = v\n\t}\n\tif v, ok := stringVal(f.CAFile); ok {\n\t\toverrides.ClusterInfo.CertificateAuthority = v\n\t}\n\tif f.Insecure != nil && *f.Insecure {\n\t\toverrides.ClusterInfo.InsecureSkipTLSVerify = true\n\t}\n\tif f.DisableCompression != nil && *f.DisableCompression {\n\t\toverrides.ClusterInfo.DisableCompression = true\n\t}\n\n\t// bind context flags\n\tif v, ok := stringVal(f.Context); ok {\n\t\toverrides.CurrentContext = v\n\t}\n\tif v, ok := stringVal(f.ClusterName); ok {\n\t\toverrides.Context.Cluster = v\n\t}\n\tif v, ok := stringVal(f.AuthInfoName); ok {\n\t\toverrides.Context.AuthInfo = v\n\t}\n\tif v, ok := stringVal(f.Namespace); ok {\n\t\toverrides.Context.Namespace = v\n\t}\n\n\tif v, ok := stringVal(f.Timeout); ok && v != \"0\" {\n\t\toverrides.Timeout = v\n\t}\n\treturn overrides\n}\n\ntype KubeconfigGetter func() (*api.Config, error)\n\ntype configGetter struct {\n\tkubeconfigGetter KubeconfigGetter\n\tdestFile         string\n}\n\nfunc (g *configGetter) Load() (*api.Config, error) {\n\treturn g.kubeconfigGetter()\n}\n\nfunc (g *configGetter) GetLoadingPrecedence() []string {\n\treturn nil\n}\n\nfunc (g *configGetter) GetStartingConfig() (*api.Config, error) {\n\treturn g.kubeconfigGetter()\n}\n\nfunc (g *configGetter) GetDefaultFilename() string {\n\tif g.destFile == \"\" {\n\t\tdestFile, err := os.CreateTemp(\"\", \"kc-*\")\n\t\tif err == nil {\n\t\t\tg.destFile = destFile.Name()\n\t\t\t_ = os.Remove(destFile.Name())\n\t\t\t_ = destFile.Close()\n\t\t}\n\t}\n\treturn g.destFile\n}\n\nfunc (g *configGetter) IsExplicitFile() bool {\n\treturn false\n}\n\nfunc (g *configGetter) GetExplicitFile() string {\n\treturn \"\"\n}\n\nfunc (g *configGetter) IsDefaultConfig(config *rest.Config) bool {\n\treturn false\n}\n\n// NewClientConfig creates a clientcmd.ClientConfig, by either reading the kubeconfig from the given configData or\n// by loading it from files as configured by the given configFlags.\nfunc NewClientConfig(ctx context.Context, configFlags *genericclioptions.ConfigFlags, configData []byte) (clientcmd.ClientConfig, error) {\n\tif len(configData) == 0 {\n\t\treturn configFlags.ToRawKubeConfigLoader(), nil\n\t}\n\tdirectConfig, err := clientcmd.NewClientConfigFromBytes(configData)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"loading kubeconfig failed: %v\", err)\n\t\treturn nil, err\n\t}\n\tconfig, err := directConfig.RawConfig()\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"raw kubeconfig failed: %v\", err)\n\t\treturn nil, err\n\t}\n\toverrides := flagOverrides(configFlags)\n\tcurrentContext := overrides.CurrentContext\n\treturn clientcmd.NewNonInteractiveClientConfig(config, currentContext, overrides, &configGetter{\n\t\tkubeconfigGetter: func() (*api.Config, error) {\n\t\t\treturn &config, nil\n\t\t},\n\t}), nil\n}\n\nfunc GetCluster(config api.Config, ctxName string) (*api.Cluster, error) {\n\tif len(config.Contexts) == 0 {\n\t\treturn nil, errcat.Config.New(\"kubeconfig has no context definition\")\n\t}\n\tif ctxName == \"\" {\n\t\tctxName = config.CurrentContext\n\t}\n\tkubeCtx, ok := config.Contexts[ctxName]\n\tif !ok {\n\t\treturn nil, errcat.Config.Newf(\"context %q does not exist in the kubeconfig\", ctxName)\n\t}\n\tif cluster, ok := config.Clusters[kubeCtx.Cluster]; ok {\n\t\treturn cluster, nil\n\t}\n\treturn nil, errcat.Config.Newf(\"the cluster %q declared in context %q does exists in the kubeconfig\", kubeCtx.Cluster, ctxName)\n}\n\nfunc newKubeconfig(\n\tctx context.Context,\n\ttpClientConfigIsFinal bool,\n\toriginalFlags,\n\teffectiveFlags map[string]string,\n\tmanagerNamespaceOverride string,\n\tconfigFlags *genericclioptions.ConfigFlags,\n\tconfigData []byte,\n) (*Kubeconfig, error) {\n\tclientConfig, err := NewClientConfig(ctx, configFlags, configData)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tconfig, err := clientConfig.RawConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tctxName := effectiveFlags[\"context\"]\n\tif ctxName == \"\" {\n\t\tctxName = config.CurrentContext\n\t}\n\n\tcluster, err := GetCluster(config, ctxName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trestConfig, err := clientConfig.ClientConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnamespace, _, err := clientConfig.Namespace()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif !tpClientConfigIsFinal {\n\t\tmanagerNamespace := managerNamespaceOverride\n\t\tif managerNamespace == \"\" {\n\t\t\tmanagerNamespace = client.GetEnv(ctx).ManagerNamespace\n\t\t}\n\t\terr = WithKubeExtension(ctx, cluster, managerNamespace)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tcs, err := kubernetes.NewForConfig(restConfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tctx = k8sapi.WithK8sInterface(ctx, cs)\n\tctx = portforward.WithRestConfig(ctx, restConfig)\n\treturn &Kubeconfig{\n\t\tContext:          ctx,\n\t\tKubeContext:      ctxName,\n\t\tServer:           cluster.Server,\n\t\tNamespace:        namespace,\n\t\tEffectiveFlagMap: effectiveFlags,\n\t\tOriginalFlagMap:  originalFlags,\n\t\tClientConfig:     clientConfig,\n\t\tRestConfig:       restConfig,\n\t}, nil\n}\n\nfunc WithKubeExtension(ctx context.Context, cluster *api.Cluster, managerNamespace string) error {\n\tcfg := client.GetConfig(ctx)\n\tvar keCfg client.Config\n\tvar data []byte\n\tif ext, ok := cluster.Extensions[configExtension].(*runtime.Unknown); ok {\n\t\tdata = bytes.TrimSpace(ext.Raw)\n\t}\n\tif len(data) > 0 {\n\t\tif kc, err := client.UnmarshalJSONConfig(data, true); err != nil {\n\t\t\t// Try with legacy kubeconfigExtension\n\t\t\tclog.Debug(ctx, \"unable to unmarshal extension as client config, trying legacy format\")\n\t\t\tke := kubeconfigExtension{}\n\t\t\tif keErr := json.Unmarshal(data, &ke); keErr != nil {\n\t\t\t\treturn errcat.Config.Errorf(err, \"unable to parse extension %s in kubeconfig\", configExtension)\n\t\t\t}\n\t\t\tclog.Debug(ctx, \"legacy format was successfully parsed\")\n\t\t\tkeCfg = ke.asConfig()\n\t\t} else {\n\t\t\tclog.Debug(ctx, \"successfully parsed extension as client config\")\n\t\t\tkeCfg = kc\n\t\t}\n\t\tif managerNamespace != \"\" {\n\t\t\tkeCfg.Cluster().DefaultManagerNamespace = managerNamespace\n\t\t}\n\t} else if managerNamespace != \"\" && managerNamespace != cfg.Cluster().DefaultManagerNamespace {\n\t\t// No kubeconfig exists but we still need a config when the managerNamespace is set.\n\t\tkeCfg = client.GetDefaultConfig()\n\t\tkeCfg.Cluster().DefaultManagerNamespace = managerNamespace\n\t}\n\tsnps := getServerNeverProxy(ctx, cluster)\n\tif len(snps) > 0 {\n\t\tif keCfg == nil {\n\t\t\tkeCfg = client.GetDefaultConfig()\n\t\t}\n\t\tkr := keCfg.Routing()\n\t\tkr.NeverProxy = append(kr.NeverProxy, snps...)\n\t}\n\tif keCfg != nil {\n\t\tkeCfg = cfg.Merge(keCfg)\n\t\tkr := keCfg.Routing()\n\t\tkr.NeverProxy = append(kr.NeverProxy, cfg.Routing().NeverProxy...)\n\t\tclient.ReplaceConfig(ctx, keCfg)\n\t}\n\treturn nil\n}\n\nfunc getServerNeverProxy(ctx context.Context, cluster *api.Cluster) []netip.Prefix {\n\tserver := cluster.Server\n\tserverURL, err := url.Parse(server)\n\tif err != nil {\n\t\t// This really shouldn't happen as we are connected to the server\n\t\tclog.Errorf(ctx, \"Unable to parse url for k8s server %s: %v\", server, err)\n\t\treturn nil\n\t}\n\thostname := serverURL.Hostname()\n\trawIP, err := netip.ParseAddr(hostname)\n\tvar ips []netip.Addr\n\tif err != nil {\n\t\tclog.Debugf(ctx, \"Hostname for k8s server %s is not an IP address\", server)\n\t\tli, err := net.LookupIP(hostname)\n\t\tif err != nil {\n\t\t\tclog.Errorf(ctx, \"Unable to do DNS lookup for k8s server %s: %v\", hostname, err)\n\t\t} else {\n\t\t\tips = make([]netip.Addr, len(li))\n\t\t\tfor i, ip := range li {\n\t\t\t\tips[i], _ = netip.AddrFromSlice(ip)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tips = []netip.Addr{rawIP}\n\t}\n\tneverProxy := make([]netip.Prefix, 0, len(ips))\n\tfor _, ip := range ips {\n\t\tif !ip.IsLoopback() {\n\t\t\tbits := 32\n\t\t\tif ip.Is6() {\n\t\t\t\tbits = 128\n\t\t\t}\n\t\t\tneverProxy = append(neverProxy, netip.PrefixFrom(ip, bits))\n\t\t}\n\t}\n\treturn neverProxy\n}\n\n// NewInClusterConfig represents an inClusterConfig.\nfunc NewInClusterConfig(c context.Context, flagMap map[string]string) (*Kubeconfig, error) {\n\tconfigFlags := genericclioptions.NewConfigFlags(false)\n\tflags := pflag.NewFlagSet(\"\", 0)\n\tconfigFlags.AddFlags(flags)\n\tfor k, v := range flagMap {\n\t\tif err := flags.Set(k, v); err != nil {\n\t\t\treturn nil, errcat.User.Errorf(err, \"error processing kubectl flag --%s=%s\", k, v)\n\t\t}\n\t}\n\n\tconfigLoader := configFlags.ToRawKubeConfigLoader()\n\trestConfig, err := configLoader.ClientConfig()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnamespace, _, err := configLoader.Namespace()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &Kubeconfig{\n\t\tContext:          c,\n\t\tNamespace:        namespace,\n\t\tServer:           restConfig.Host,\n\t\tEffectiveFlagMap: flagMap,\n\t\tOriginalFlagMap:  flagMap,\n\t\tRestConfig:       restConfig,\n\t\tClientConfig:     configLoader,\n\t}, nil\n}\n\n// ContextServiceAndFlagsEqual determines if this instance is equal to the given instance with respect to context,\n// server, and flag arguments.\nfunc (kf *Kubeconfig) ContextServiceAndFlagsEqual(okf *Kubeconfig) bool {\n\treturn kf != nil && okf != nil &&\n\t\tkf.KubeContext == okf.KubeContext &&\n\t\tkf.Server == okf.Server &&\n\t\tmaps.Equal(kf.EffectiveFlagMap, okf.EffectiveFlagMap)\n}\n\nfunc (kf *Kubeconfig) GetKubeContext() string {\n\treturn kf.KubeContext\n}\n\nfunc (kf *Kubeconfig) GetClientConfig() clientcmd.ClientConfig {\n\treturn kf.ClientConfig\n}\n\nfunc (kf *Kubeconfig) GetRestConfig() *rest.Config {\n\treturn kf.RestConfig\n}\n"
  },
  {
    "path": "pkg/client/k8s/connect.go",
    "content": "package k8s\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/keepalive\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\tk8serrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/agent\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/portforward\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\tgrpcClient \"github.com/telepresenceio/telepresence/v2/pkg/grpc/client\"\n)\n\nfunc (kc *Cluster) ConnectToManager(dialCtx context.Context, namespace string) (conn *grpc.ClientConn, name string, ver semver.Version, err error) {\n\tgrpcAddr := net.JoinHostPort(\"svc/traffic-manager.\"+namespace, \"api\")\n\n\tdialCtx, cancel := client.GetConfig(kc).Timeouts().TimeoutContext(dialCtx, client.TimeoutTrafficManagerConnect)\n\tdefer cancel()\n\n\tpap, err := portforward.ResolveSvcToPod(kc, \"traffic-manager\", namespace, \"8081\")\n\tif err != nil {\n\t\tse := &k8serrors.StatusError{}\n\t\tif errors.As(err, &se) {\n\t\t\tif se.Status().Code == http.StatusNotFound {\n\t\t\t\treturn nil, \"\", ver, errcat.User.New(\"traffic manager not found, if it is not installed, please run 'telepresence helm install'. \" +\n\t\t\t\t\t\"If it is installed, try connecting with a --manager-namespace to point telepresence to the namespace it's installed in.\")\n\t\t\t}\n\t\t}\n\t\treturn nil, \"\", semver.Version{}, err\n\t}\n\n\tconn, err = kc.dialGRPC(dialCtx, grpcAddr, pap)\n\tif err != nil {\n\t\treturn nil, \"\", ver, err\n\t}\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tconn.Close()\n\t\t} else {\n\t\t\tclog.Infof(kc, \"Connected to Manager %s\", ver)\n\t\t}\n\t}()\n\n\tvi, err := getVersion(dialCtx, manager.NewManagerClient(conn))\n\tif err != nil {\n\t\treturn conn, \"\", ver, client.CheckTimeout(dialCtx, fmt.Errorf(\"dial manager: %w\", err))\n\t}\n\tverStr := strings.TrimPrefix(vi.Version, \"v\")\n\tver, err = semver.Parse(verStr)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to parse manager version %q: %w\", verStr, err)\n\t}\n\treturn conn, vi.Name, ver, err\n}\n\ntype versionAPI interface {\n\tVersion(context.Context, *empty.Empty, ...grpc.CallOption) (*manager.VersionInfo2, error)\n}\n\nfunc (kc *Cluster) ConnectToAgent(\n\tdialCtx context.Context,\n\tpodName string,\n\tport uint16,\n\tpodID types.UID,\n) (*grpc.ClientConn, agent.AgentClient, *manager.VersionInfo2, error) {\n\tvar grpcAddr string\n\tif podID == \"\" {\n\t\tgrpcAddr = fmt.Sprintf(\"pod/%s.%s:%d\", podName, kc.Namespace, port)\n\t} else {\n\t\tgrpcAddr = fmt.Sprintf(\"pod/%s.%s:%d#%s\", podName, kc.Namespace, port, podID)\n\t}\n\tconn, err := kc.dialGRPC(dialCtx, grpcAddr, nil)\n\tif err != nil {\n\t\treturn nil, nil, nil, err\n\t}\n\tmClient := agent.NewAgentClient(conn)\n\tvi, err := getVersion(dialCtx, mClient)\n\tif err != nil {\n\t\terr = client.CheckTimeout(dialCtx, fmt.Errorf(\"dial agent: %w\", err))\n\t\tconn.Close()\n\t}\n\treturn conn, mClient, vi, err\n}\n\nfunc (kc *Cluster) dialGRPC(dialCtx context.Context, address string, knownPod *portforward.PodAddress) (*grpc.ClientConn, error) {\n\treturn grpcClient.DialGRPC(dialCtx, portforward.K8sPFScheme+\":///\"+address, grpc.WithContextDialer(portforward.Dialer(kc)),\n\t\tgrpc.WithResolvers(portforward.NewResolver(kc, knownPod)),\n\t\tgrpc.WithKeepaliveParams(keepalive.ClientParameters{Time: 24 * time.Hour, Timeout: 20 * time.Second}),\n\t\tgrpc.WithIdleTimeout(0),\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()))\n}\n\nfunc getVersion(ctx context.Context, gc versionAPI) (*manager.VersionInfo2, error) {\n\t// At this point, we are connected to the traffic-manager. We use the shorter API timeout\n\ttos := client.GetConfig(ctx).Timeouts()\n\tb := backoff.ExponentialBackOff{\n\t\tInitialInterval:     500 * time.Millisecond,\n\t\tRandomizationFactor: backoff.DefaultRandomizationFactor,\n\t\tMultiplier:          backoff.DefaultMultiplier,\n\t\tMaxInterval:         2 * time.Second,\n\t\tMaxElapsedTime:      tos.Get(client.TimeoutTrafficManagerAPI),\n\t\tStop:                backoff.Stop,\n\t\tClock:               backoff.SystemClock,\n\t}\n\tb.Reset()\n\tvar vi *manager.VersionInfo2\n\terr := backoff.Retry(func() (err error) {\n\t\tvi, err = gc.Version(ctx, &empty.Empty{})\n\t\treturn err\n\t}, backoff.WithContext(&b, ctx))\n\tif err == nil {\n\t\tclog.Infof(ctx, \"Connected to %s %s\", vi.Name, vi.Version)\n\t}\n\treturn vi, err\n}\n"
  },
  {
    "path": "pkg/client/logging/cached_timed_level.go",
    "content": "package logging\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cache\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n)\n\ntype cachedTLData struct {\n\tLevel   slog.Level `json:\"level\"`\n\tExpires int64      // Seconds since epoch\n}\n\nfunc SetAndStoreTimedLevel(ctx context.Context, tl log.TimedLevel, level slog.Level, duration time.Duration, procName string) error {\n\ttl.Set(ctx, level, duration)\n\tcd := cachedTLData{Level: level}\n\tif duration > 0 {\n\t\tcd.Expires = time.Now().Add(duration).Unix()\n\t}\n\treturn cache.SaveToUserCache(ctx, &cd, procName+\".loglevel\", cache.Public)\n}\n\nfunc LoadTimedLevelFromCache(ctx context.Context, tl log.TimedLevel, procName string) error {\n\tfile := procName + \".loglevel\"\n\tcd := cachedTLData{}\n\tif err := cache.LoadFromUserCache(ctx, &cd, file); err != nil {\n\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\terr = nil\n\t\t}\n\t\treturn err\n\t}\n\tif cd.Expires == 0 {\n\t\ttl.Set(ctx, cd.Level, 0)\n\t} else if duration := time.Until(time.Unix(cd.Expires, 0)); duration > 0 {\n\t\ttl.Set(ctx, cd.Level, duration)\n\t} else {\n\t\t// Time has expired, just drop the cache.\n\t\t_ = cache.DeleteFromUserCache(ctx, file)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/logging/dup_test.go",
    "content": "package logging\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc head(str string, n int) string {\n\tend := 0\n\tfor i := 0; i < n; i++ {\n\t\tnl := strings.IndexByte(str[end:], '\\n')\n\t\tif nl < 0 {\n\t\t\treturn str\n\t\t}\n\t\tend += nl + 1\n\t}\n\treturn str[:end]\n}\n\nfunc TestDupStd(t *testing.T) {\n\tdirname := t.TempDir()\n\n\tctx := context.Background()\n\tcmd := exec.CommandContext(ctx, os.Args[0], \"-test.v\", \"-test.run=\"+t.Name()+\"Helper\", \"--\", dirname)\n\tcmd.Env = append(os.Environ(),\n\t\t\"GO_WANT_HELPER_PROCESS=1\")\n\n\terr := cmd.Run()\n\tvar eerr *exec.ExitError\n\trequire.ErrorAs(t, err, &eerr)\n\trequire.True(t, eerr.Exited())\n\trequire.Equal(t, 2, eerr.ExitCode())\n\n\tcontent, err := os.ReadFile(filepath.Join(dirname, \"log.txt\"))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"this is stdout\\nthis is stderr\\npanic: this is panic\\n\",\n\t\thead(string(content), 3))\n}\n\nfunc TestMain(m *testing.M) {\n\tif os.Getenv(\"GO_WANT_HELPER_PROCESS\") == \"1\" {\n\t\tos.Exit(testDupStdHelper())\n\t}\n\tos.Exit(m.Run())\n}\n\nfunc testDupStdHelper() int {\n\targs := os.Args\n\tfor len(args) > 0 {\n\t\tif args[0] == \"--\" {\n\t\t\targs = args[1:]\n\t\t\tbreak\n\t\t}\n\t\targs = args[1:]\n\t}\n\tif len(args) != 1 {\n\t\tfmt.Fprintf(os.Stderr, \"expected exactly 1 argument, got %d\\n\", len(args))\n\t\treturn 1\n\t}\n\n\tdirname := args[0]\n\n\tfile, err := os.OpenFile(filepath.Join(dirname, \"log.txt\"), os.O_CREATE|os.O_WRONLY, 0o666)\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"open: %v\\n\", err)\n\t\treturn 1\n\t}\n\n\tif err := dupStdOut(file); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"dup: %v\\n\", err)\n\t\treturn 1\n\t}\n\n\tif err := dupStdErr(file); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"dup: %v\\n\", err)\n\t\treturn 1\n\t}\n\n\tfmt.Fprintln(os.Stdout, \"this is stdout\")\n\tfmt.Fprintln(os.Stderr, \"this is stderr\")\n\tpanic(\"this is panic\")\n}\n"
  },
  {
    "path": "pkg/client/logging/dup_unix.go",
    "content": "//go:build !windows\n\npackage logging\n\nimport (\n\t\"os\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\n// dupStdOut ensures that anything written to stdout will end up in the given file.\nfunc dupStdOut(file *os.File) error {\n\t// https://github.com/golang/go/issues/325\n\tif err := unix.Dup2(int(file.Fd()), 1); err != nil {\n\t\treturn err\n\t}\n\tos.Stdout = file\n\treturn nil\n}\n\n// dupStdErr ensures that anything written to stderr will end up in the given file.\nfunc dupStdErr(file *os.File) error {\n\t// https://github.com/golang/go/issues/325\n\tif err := unix.Dup2(int(file.Fd()), 2); err != nil {\n\t\treturn err\n\t}\n\tos.Stderr = file\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/logging/dup_windows.go",
    "content": "package logging\n\nimport (\n\t\"os\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\nfunc dupStdOut(file *os.File) error {\n\tif err := windows.SetStdHandle(windows.STD_OUTPUT_HANDLE, windows.Handle(file.Fd())); err != nil {\n\t\treturn err\n\t}\n\tos.Stdout = file\n\treturn nil\n}\n\nfunc dupStdErr(file *os.File) error {\n\t// https://stackoverflow.com/questions/34772012/capturing-panic-in-golang/34772516\n\tif err := windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(file.Fd())); err != nil {\n\t\treturn err\n\t}\n\tos.Stderr = file\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/logging/initcontext.go",
    "content": "package logging\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\tstdLog \"log\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/handler\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\n// rotatingFileForTest exposes internals to initcontext_test.go.\nvar rotatingFileForTest *RotatingFile //nolint:gochecknoglobals // used by unit tests only\n\ntype splitErrorWriter struct {\n\toutWriter io.Writer\n\terrWriter io.Writer\n}\n\nfunc (w *splitErrorWriter) Write(level slog.Level, p []byte) (n int, err error) {\n\tif level >= slog.LevelError {\n\t\treturn w.errWriter.Write(p)\n\t}\n\treturn w.outWriter.Write(p)\n}\n\n// InitContext sets up standard Telepresence logging for a background process.\nfunc InitContext(ctx context.Context, logFile string, logLevel slog.Level, strategy RotationStrategy, captureStd bool) (context.Context, error) {\n\tctx = clog.WithTreeLevel(ctx, logLevel)\n\n\tvar opts []handler.Option\n\tinitStdLog := false\n\tswitch logFile {\n\tcase \"stdout\":\n\t\topts = append(opts, handler.TimeFormat(\"15:04:05.0000\"), handler.Output(os.Stdout))\n\tcase \"\", \"-\", \"stderr\":\n\t\topts = append(opts, handler.TimeFormat(\"15:04:05.0000\"), handler.Output(os.Stderr))\n\tcase \"managed\":\n\t\t// \"managed\" is a special case used by the daemon to log to stdout and stderr. It's\n\t\t// assumed that the caller will add a timestamp, and that level is implicit for errors.\n\t\topts = append(opts, handler.TimeFormat(\"\"), handler.HideLevel(slog.LevelError), handler.LevelOutput(&splitErrorWriter{os.Stdout, os.Stderr}))\n\tcase \"std\":\n\t\t// \"std\" is a special case used by the daemon to log to stdout and stderr. Contrary to\n\t\t// \"managed\", it's not assumed that the caller will add a timestamp or that the level is implicit.\n\t\topts = append(opts, handler.TimeFormat(\"2006-01-02 15:04:05.0000\"), handler.LevelOutput(&splitErrorWriter{os.Stdout, os.Stderr}))\n\tdefault:\n\t\tinitStdLog = true\n\t\tmaxFiles := uint16(5)\n\n\t\t// TODO: Also make this a configurable setting in config.yml\n\t\tif me := os.Getenv(\"TELEPRESENCE_MAX_LOGFILES\"); me != \"\" {\n\t\t\tif mx, err := strconv.Atoi(me); err == nil && mx >= 0 {\n\t\t\t\tmaxFiles = uint16(mx)\n\t\t\t}\n\t\t}\n\n\t\t// Validate the path before using it.\n\t\tlogFile, err := ValidateLogFilePath(logFile)\n\t\tif err != nil {\n\t\t\treturn ctx, err\n\t\t}\n\t\trf, err := OpenRotatingFile(ctx, logFile, \"20060102T150405\", true, 0o600, strategy, maxFiles)\n\t\tif err != nil {\n\t\t\treturn ctx, err\n\t\t}\n\t\trotatingFileForTest = rf\n\t\tif captureStd {\n\t\t\trfFile := rf.file.(*os.File)\n\t\t\terr = dupStdOut(rfFile)\n\t\t\tif err != nil {\n\t\t\t\t_ = rf.Close()\n\t\t\t\treturn ctx, err\n\t\t\t}\n\t\t\terr = dupStdErr(rfFile)\n\t\t\tif err != nil {\n\t\t\t\t_ = rf.Close()\n\t\t\t\treturn ctx, err\n\t\t\t}\n\t\t}\n\t\topts = append(opts, handler.TimeFormat(\"2006-01-02 15:04:05.0000\"), handler.Output(rf))\n\t}\n\n\tsl := slog.New(handler.NewText(append(opts, handler.LevelEnabler(clog.TreeEnabled))...))\n\tslog.SetDefault(sl)\n\tctx = clog.WithLogger(ctx, sl)\n\tif initStdLog {\n\t\tstl := clog.StdLogger(ctx, logLevel)\n\t\tstdLog.SetOutput(stl.Writer())\n\t\tstdLog.SetFlags(stl.Flags())\n\t\tstdLog.SetPrefix(\"stdlog : \")\n\t}\n\treturn ctx, nil\n}\n\n// ValidateLogFilePath ensures that the log file path is valid and that the parent directory exists or can be created.\nfunc ValidateLogFilePath(logFile string) (string, error) {\n\t// Convert to an absolute path. Abs ensures Clean.\n\tlogFile, err := filepath.Abs(logFile)\n\tif err != nil {\n\t\treturn \"\", errcat.User.Errorf(err, \"invalid log file path\")\n\t}\n\n\t// Verify that the parent directory exists or can be created\n\tdir := filepath.Dir(logFile)\n\tif err = os.MkdirAll(dir, 0o700); err != nil {\n\t\treturn \"\", errcat.User.Errorf(err, \"cannot create log directory %q\", dir)\n\t}\n\treturn logFile, nil\n}\n\nfunc SummarizeLog(ctx context.Context, name string) (string, error) {\n\tfilename := filepath.Join(filelocation.AppUserLogDir(ctx), name+\".log\")\n\tfile, err := dos.Open(ctx, filename)\n\tif err != nil {\n\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\terr = nil\n\t\t}\n\t\treturn \"\", err\n\t}\n\tdefer file.Close()\n\tscanner := bufio.NewScanner(file)\n\n\terrorCount := 0\n\tfor scanner.Scan() {\n\t\t// XXX: is there a better way to detect error lines?\n\t\ttxt := scanner.Text()\n\t\tparts := strings.Fields(txt)\n\t\tif len(parts) < 3 {\n\t\t\tcontinue\n\t\t}\n\t\tswitch parts[2] {\n\t\tcase \"error\":\n\t\t\terrorCount++\n\t\tcase \"info\":\n\t\t\tif strings.Contains(txt, \"-- Starting new session\") {\n\t\t\t\t// Start over. No use counting errors from previous sessions\n\t\t\t\terrorCount = 0\n\t\t\t}\n\t\t}\n\t}\n\tif errorCount == 0 {\n\t\treturn \"\", nil\n\t}\n\tdesc := fmt.Sprintf(\"%d error\", errorCount)\n\tif errorCount > 1 {\n\t\tdesc += \"s\"\n\t}\n\n\treturn fmt.Sprintf(\"See logs for details (%s found): %q\", desc, filename), nil\n}\n"
  },
  {
    "path": "pkg/client/logging/initcontext_test.go",
    "content": "package logging\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"log/slog\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"testing\"\n\t\"testing/synctest\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc TestInitContext(t *testing.T) {\n\tconst logName = \"testing\"\n\n\ttestSetup := func(t *testing.T) (ctx context.Context, logDir, logFile string) {\n\t\tt.Helper()\n\t\tctx = testutil.NewContext(t, false)\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\tenv, err := client.LoadEnv()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tctx = client.WithEnv(ctx, &env)\n\n\t\t// Ensure that we use a temporary log dir\n\t\tlogDir = t.TempDir()\n\t\tctx = filelocation.WithAppUserLogDir(ctx, logDir)\n\n\t\tcfg, err := client.LoadConfig(ctx)\n\t\trequire.NoError(t, err)\n\t\tctx = client.WithConfig(ctx, cfg)\n\n\t\t// Ensure that we never consider Stdout to be a terminal\n\t\tsaveIsTerminal := IsTerminal\n\t\tIsTerminal = func(int) bool { return false }\n\t\tt.Cleanup(func() { IsTerminal = saveIsTerminal })\n\n\t\t// InitContext overrides both file descriptors 1/2 and the variables\n\t\t// os.Stdout/os.Stdin; so they need to be backed up and restored.\n\t\tsaveStdout := os.Stdout\n\t\tsaveStderr := os.Stderr\n\t\trestoreStd, err := dupStd()\n\t\trequire.NoError(t, err)\n\t\tt.Cleanup(func() {\n\t\t\tos.Stdout = saveStdout\n\t\t\tos.Stderr = saveStderr\n\t\t\trestoreStd()\n\t\t\tcancel()\n\t\t})\n\n\t\treturn ctx, logDir, filepath.Join(logDir, logName+\".log\")\n\t}\n\n\tt.Run(\"stdout and stderr\", func(t *testing.T) {\n\t\tsynctest.Test(t, func(t *testing.T) {\n\t\t\tctx, _, logFile := testSetup(t)\n\t\t\tcheck := require.New(t)\n\n\t\t\tclog.Info(ctx, \"test setup\")\n\n\t\t\tc, err := InitContext(ctx, logFile, slog.LevelInfo, NewRotateOnce(), true)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.NotNil(c)\n\t\t\trequire.FileExists(t, logFile)\n\n\t\t\tinfoMsg := \"info\"\n\t\t\tfmt.Fprintln(os.Stdout, infoMsg)\n\t\t\ttime.Sleep(10 * time.Millisecond) // Ensure that message is logged before the next is produced\n\n\t\t\terrMsg := \"error\"\n\t\t\tfmt.Fprintln(os.Stderr, errMsg)\n\t\t\ttime.Sleep(30 * time.Millisecond)\n\t\t\t_ = rotatingFileForTest.Close()\n\n\t\t\tbs, err := os.ReadFile(logFile)\n\t\t\tcheck.NoError(err)\n\t\t\ts := string(bs)\n\t\t\tcheck.Contains(s, infoMsg)\n\t\t\tcheck.Contains(s, errMsg)\n\t\t})\n\t})\n\n\tt.Run(\"captures output of builtin functions\", func(t *testing.T) {\n\t\tctx, _, logFile := testSetup(t)\n\t\tcheck := require.New(t)\n\n\t\tc, err := InitContext(ctx, logFile, slog.LevelInfo, NewRotateOnce(), true)\n\t\tcheck.NoError(err)\n\t\tcheck.NotNil(c)\n\n\t\tmsg := \"some message\"\n\t\tprintln(msg) //nolint:forbidigo // we're testing this builtin function\n\t\tcheck.FileExists(logFile)\n\t\ttime.Sleep(30 * time.Millisecond)\n\t\t_ = rotatingFileForTest.Close()\n\t\tbs, err := os.ReadFile(logFile)\n\t\tcheck.NoError(err)\n\t\tcheck.Contains(string(bs), msg)\n\t})\n\n\tt.Run(\"captures output of standard logger\", func(t *testing.T) {\n\t\tctx, _, logFile := testSetup(t)\n\t\tcheck := require.New(t)\n\n\t\tc, err := InitContext(ctx, logFile, slog.LevelInfo, NewRotateOnce(), true)\n\t\tcheck.NoError(err)\n\t\tcheck.NotNil(c)\n\n\t\tmsg := \"some message\"\n\t\tlog.Print(msg)\n\t\ttime.Sleep(100 * time.Millisecond)\n\t\tcheck.FileExists(logFile)\n\t\t_ = rotatingFileForTest.Close()\n\n\t\tbs, err := os.ReadFile(logFile)\n\t\tcheck.NoError(err)\n\t\tcheck.Contains(string(bs), fmt.Sprintf(\"INFO  stdlog : %s\\n\", msg))\n\t})\n\n\tt.Run(\"next session rotates on write\", func(t *testing.T) {\n\t\tsynctest.Test(t, func(t *testing.T) {\n\t\t\tctx, logDir, logFile := testSetup(t)\n\t\t\tcheck := require.New(t)\n\n\t\t\tc, err := InitContext(ctx, logFile, slog.LevelInfo, NewRotateOnce(), false)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.NotNil(c)\n\t\t\tinfoMsg := \"info message\"\n\t\t\tclog.Info(c, infoMsg)\n\t\t\t_ = rotatingFileForTest.Close()\n\t\t\ttime.Sleep(time.Second)\n\n\t\t\tc, err = InitContext(ctx, logFile, slog.LevelInfo, NewRotateOnce(), false)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.NotNil(c)\n\t\t\tclog.Info(c, infoMsg)\n\t\t\t_ = rotatingFileForTest.Close()\n\t\t\tcheck.FileExists(logFile)\n\n\t\t\tinfoTs := time.Now().Format(\"2006-01-02 15:04:05.0000\")\n\t\t\tbackupFile := filepath.Join(logDir, fmt.Sprintf(\"%s-%s.log\", logName, time.Now().Format(\"20060102T150405\")))\n\t\t\tcheck.FileExists(backupFile)\n\n\t\t\tbs, err := os.ReadFile(logFile)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.Contains(string(bs), fmt.Sprintf(\"%s INFO  %s\\n\", infoTs, infoMsg))\n\t\t})\n\t})\n\n\tt.Run(\"birthtime updates after rotate\", func(t *testing.T) {\n\t\tsynctest.Test(t, func(t *testing.T) {\n\t\t\tctx, _, logFile := testSetup(t)\n\t\t\tcheck := require.New(t)\n\n\t\t\tc, err := InitContext(ctx, logFile, slog.LevelInfo, NewRotateOnce(), false)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.NotNil(c)\n\t\t\tclog.Info(c, \"info message\")\n\t\t\tcheck.NotNil(rotatingFileForTest)\n\t\t\t_ = rotatingFileForTest.Close()\n\t\t\tbt1 := rotatingFileForTest.birthTime\n\n\t\t\tc, err = InitContext(ctx, logFile, slog.LevelInfo, NewRotateOnce(), false)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.NotNil(c)\n\t\t\tclog.Info(c, \"info message\")\n\t\t\tcheck.NotNil(rotatingFileForTest)\n\t\t\t_ = rotatingFileForTest.Close()\n\t\t\tbt2 := rotatingFileForTest.birthTime\n\t\t\tcheck.Equal(bt1, bt2)\n\t\t})\n\t})\n\n\tt.Run(\"next session appends when no rotate\", func(t *testing.T) {\n\t\tsynctest.Test(t, func(t *testing.T) {\n\t\t\tctx, _, logFile := testSetup(t)\n\t\t\tcheck := require.New(t)\n\n\t\t\tc, err := InitContext(ctx, logFile, slog.LevelInfo, RotateNever, false)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.NotNil(c)\n\t\t\tinfoMsg1 := \"info message 1\"\n\t\t\tclog.Info(c, infoMsg1)\n\t\t\t_ = rotatingFileForTest.Close()\n\n\t\t\tc, err = InitContext(ctx, logFile, slog.LevelInfo, RotateNever, false)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.NotNil(c)\n\t\t\tinfoMsg2 := \"info message 2\"\n\t\t\tclog.Info(c, infoMsg2)\n\t\t\t_ = rotatingFileForTest.Close()\n\n\t\t\tbs, err := os.ReadFile(logFile)\n\t\t\tcheck.NoError(err)\n\t\t\tinfoTs := time.Now().Format(\"2006-01-02 15:04:05.0000\")\n\t\t\tcheck.Contains(string(bs), fmt.Sprintf(\"%s INFO  %s\\n\", infoTs, infoMsg1))\n\t\t\tcheck.Contains(string(bs), fmt.Sprintf(\"%s INFO  %s\\n\", infoTs, infoMsg2))\n\t\t})\n\t})\n\n\tt.Run(\"old files are removed\", func(t *testing.T) {\n\t\tsynctest.Test(t, func(t *testing.T) {\n\t\t\tctx, logDir, logFile := testSetup(t)\n\t\t\tcheck := require.New(t)\n\n\t\t\tmaxFiles := 5\n\t\t\tif me := os.Getenv(\"TELEPRESENCE_MAX_LOGFILES\"); me != \"\" {\n\t\t\t\tif mx, err := strconv.Atoi(me); err == nil && mx >= 0 {\n\t\t\t\t\tmaxFiles = mx\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor i := 0; i < maxFiles+2; i++ {\n\t\t\t\ttime.Sleep(24 * time.Hour)\n\t\t\t\tc, err := InitContext(ctx, logFile, slog.LevelInfo, NewRotateOnce(), false)\n\t\t\t\tcheck.NoError(err)\n\t\t\t\tcheck.NotNil(c)\n\t\t\t\tinfoMsg := \"info message\"\n\t\t\t\tclog.Info(c, infoMsg)\n\t\t\t\t_ = rotatingFileForTest.Close()\n\t\t\t}\n\t\t\t// Give file remover some time to finish\n\t\t\ttime.Sleep(100 * time.Millisecond)\n\n\t\t\tfiles, err := os.ReadDir(logDir)\n\t\t\tcheck.NoError(err)\n\t\t\tcheck.Equal(maxFiles, len(files))\n\t\t})\n\t})\n}\n"
  },
  {
    "path": "pkg/client/logging/initcontext_unix_test.go",
    "content": "//go:build !windows\n\npackage logging\n\nimport (\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc dupStd() (func(), error) {\n\tstdoutFd, err := unix.Dup(1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstderrFd, err := unix.Dup(2)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn func() {\n\t\t_ = unix.Dup2(stdoutFd, 1)\n\t\t_ = unix.Dup2(stderrFd, 2)\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/client/logging/initcontext_windows_test.go",
    "content": "package logging\n\nfunc dupStd() (func(), error) {\n\treturn func() {}, nil\n}\n"
  },
  {
    "path": "pkg/client/logging/rotatingfile.go",
    "content": "package logging\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\n// A RotationStrategy answers the question if it is time to rotate the file now. It is called prior to every write\n// so it needs to be fairly quick.\ntype RotationStrategy interface {\n\tRotateNow(file *RotatingFile, writeSize int) bool\n}\n\ntype rotateNever int\n\n// RotateNever strategy will always answer false to the RotateNow question.\nconst RotateNever = rotateNever(0)\n\nfunc (rotateNever) RotateNow(_ *RotatingFile, _ int) bool {\n\treturn false\n}\n\n// A rotateOnce ensures that the file is rotated exactly once if it is of non-zero size when the\n// first call to Write() arrives.\ntype rotateOnce struct {\n\tcalled bool\n}\n\nfunc NewRotateOnce() RotationStrategy {\n\treturn &rotateOnce{}\n}\n\nfunc (r *rotateOnce) RotateNow(rf *RotatingFile, _ int) bool {\n\tif r.called {\n\t\treturn false\n\t}\n\tr.called = true\n\treturn rf.Size() > 0\n}\n\ntype rotateDaily int\n\n// RotateDaily strategy will ensure that the file is rotated if it is of non-zero size when a call\n// to Write() arrives on a day different from the day when the current file was created.\nconst RotateDaily = rotateDaily(0)\n\nfunc (rotateDaily) RotateNow(rf *RotatingFile, _ int) bool {\n\tif rf.Size() == 0 {\n\t\treturn false\n\t}\n\tbt := rf.BirthTime()\n\treturn time.Now().In(bt.Location()).Day() != rf.BirthTime().Day()\n}\n\ntype RotatingFile struct {\n\tctx         context.Context\n\tfileMode    fs.FileMode\n\tdirName     string\n\tfileName    string\n\ttimeFormat  string\n\tlocalTime   bool\n\tmaxFiles    uint16\n\tstrategy    RotationStrategy\n\tmutex       sync.Mutex\n\tremoveMutex sync.Mutex\n\n\t// file is the current file. It is never nil\n\tfile dos.File\n\n\t// size is the number of bytes written to the current file.\n\tsize int64\n\n\t// birthTime is the time when the current file was first created\n\tbirthTime time.Time\n}\n\n// OpenRotatingFile opens a file with the given name after first having created the directory that it\n// resides in and all parent directories. The file is opened write only.\n//\n// Parameters:\n//\n//   - ctx: context used to close the file.\n//   - logFilePath: full path to the directory of the log file and its backups\n//   - timeFormat: the format to use for the timestamp that is added to rotated files\n//   - localTime: if true, use local time in timestamps, if false, use UTC\n//   - fileMode: the mode to use when creating new files the file\n//   - strategy:  determines when a rotation should take place\n//   - maxFiles: maximum number of files in rotation, including the currently active logfile. A value of zero means unlimited.\nfunc OpenRotatingFile(\n\tctx context.Context,\n\tlogfilePath string,\n\ttimeFormat string,\n\tlocalTime bool,\n\tfileMode fs.FileMode,\n\tstrategy RotationStrategy,\n\tmaxFiles uint16,\n) (*RotatingFile, error) {\n\tlogfileDir, logfileBase := filepath.Split(logfilePath)\n\n\tvar err error\n\tif err = dos.MkdirAll(ctx, logfileDir, 0o755); err != nil {\n\t\treturn nil, err\n\t}\n\n\trf := &RotatingFile{\n\t\tctx:        ctx,\n\t\tdirName:    logfileDir,\n\t\tfileName:   logfileBase,\n\t\tfileMode:   fileMode,\n\t\tstrategy:   strategy,\n\t\tlocalTime:  localTime,\n\t\ttimeFormat: timeFormat,\n\t\tmaxFiles:   maxFiles,\n\t}\n\n\t// Try to open existing file for append.\n\tif rf.file, err = dos.OpenFile(ctx, logfilePath, os.O_WRONLY|os.O_APPEND, rf.fileMode); err != nil {\n\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\t// There is no existing file, go ahead and create a new one.\n\t\t\tif err = rf.openNew(nil, \"\"); err == nil {\n\t\t\t\treturn rf, nil\n\t\t\t}\n\t\t}\n\t\treturn nil, err\n\t}\n\t// We successfully opened the existing file, get it plugged in.\n\tstat, err := FStat(rf.file)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to stat %s: %w\", logfilePath, err)\n\t}\n\trf.birthTime = stat.BirthTime()\n\trf.size = stat.Size()\n\trf.afterOpen()\n\tgo func() {\n\t\t<-ctx.Done()\n\t\t_ = rf.Close()\n\t}()\n\treturn rf, nil\n}\n\n// BirthTime returns the time when the current file was created. The time will be local if\n// the file was opened with localTime == true and UTC otherwise.\nfunc (rf *RotatingFile) BirthTime() time.Time {\n\trf.mutex.Lock()\n\tbt := rf.birthTime\n\trf.mutex.Unlock()\n\treturn bt\n}\n\n// Close implements io.Closer.\nfunc (rf *RotatingFile) Close() error {\n\treturn rf.file.Close()\n}\n\n// Rotate closes the currently opened file and renames it by adding a timestamp between the file name\n// and its extension. A new file empty file is then opened to receive subsequent data.\nfunc (rf *RotatingFile) Rotate() (err error) {\n\trf.mutex.Lock()\n\tdefer rf.mutex.Unlock()\n\treturn rf.rotate()\n}\n\n// Size returns the size of the current file.\nfunc (rf *RotatingFile) Size() int64 {\n\trf.mutex.Lock()\n\tsz := rf.size\n\trf.mutex.Unlock()\n\treturn sz\n}\n\n// Write implements io.Writer.\nfunc (rf *RotatingFile) Write(data []byte) (int, error) {\n\trotateNow := rf.strategy.RotateNow(rf, len(data))\n\trf.mutex.Lock()\n\tdefer rf.mutex.Unlock()\n\n\tif rotateNow {\n\t\tif err := rf.rotate(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\tl, err := rf.file.Write(data)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\trf.size += int64(l)\n\treturn l, nil\n}\n\nfunc (rf *RotatingFile) afterOpen() {\n\tgo rf.removeOldFiles()\n}\n\nfunc (rf *RotatingFile) fileTime(t time.Time) time.Time {\n\tif rf.localTime {\n\t\tt = t.Local()\n\t} else {\n\t\tt = t.UTC()\n\t}\n\treturn t\n}\n\nfunc (rf *RotatingFile) openNew(prevInfo SysInfo, backupName string) (err error) {\n\tfullPath := filepath.Join(rf.dirName, rf.fileName)\n\tvar flag int\n\tif rf.file == nil {\n\t\tflag = os.O_CREATE | os.O_WRONLY | os.O_TRUNC\n\t} else {\n\t\t// Open file with a different name so that a tail -F on the original doesn't fail with a permission denied\n\t\ttmp := fullPath + \".tmp\"\n\t\tvar tmpFile dos.File\n\t\tif tmpFile, err = dos.OpenFile(rf.ctx, tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, rf.fileMode); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to createFile %s: %w\", tmp, err)\n\t\t}\n\n\t\tvar si SysInfo\n\t\tsi, err = FStat(tmpFile)\n\t\t_ = tmpFile.Close()\n\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to stat %s: %w\", tmp, err)\n\t\t}\n\n\t\tif prevInfo != nil && !prevInfo.HaveSameOwnerAndGroup(si) {\n\t\t\tif err = prevInfo.SetOwnerAndGroup(tmp); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to SetOwnerAndGroup for %s: %w\", tmp, err)\n\t\t\t}\n\t\t}\n\n\t\tif err = rf.file.Close(); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to close %s: %w\", rf.file.Name(), err)\n\t\t}\n\t\tif err = dos.Rename(rf.ctx, fullPath, backupName); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to rename %s to %s: %w\", fullPath, backupName, err)\n\t\t}\n\t\tif err = dos.Rename(rf.ctx, tmp, fullPath); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to rename %s to %s: %w\", tmp, fullPath, err)\n\t\t}\n\t\t// Need to restore birth time on Windows since it retains the birt time of the\n\t\t// overwritten target of the rename operation.\n\t\tif err = restoreCTimeAfterRename(fullPath, si.BirthTime()); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to restore creation time of %s to %s: %w\", tmp, si.BirthTime(), err)\n\t\t}\n\t\tflag = os.O_WRONLY | os.O_APPEND\n\t}\n\tif rf.file, err = dos.OpenFile(rf.ctx, fullPath, flag, rf.fileMode); err != nil {\n\t\treturn fmt.Errorf(\"failed to open file %s: %w\", fullPath, err)\n\t}\n\trf.birthTime = rf.fileTime(time.Now())\n\trf.size = 0\n\trf.afterOpen()\n\treturn nil\n}\n\n// removeOldFiles checks how many files that currently exists (backups + current log file) with the same\n// name as this RotatingFile and then, as long as the number of files exceed the maxFiles given to  the\n// constructor, it will continuously remove the oldest file.\n//\n// This function should typically run in its own goroutine.\nfunc (rf *RotatingFile) removeOldFiles() {\n\trf.removeMutex.Lock()\n\tdefer rf.removeMutex.Unlock()\n\n\tfiles, err := dos.ReadDir(rf.ctx, rf.dirName)\n\tif err != nil {\n\t\treturn\n\t}\n\text := filepath.Ext(rf.fileName)\n\tpfx := rf.fileName[:len(rf.fileName)-len(ext)] + \"-\"\n\n\t// Use a map with unix nanosecond timestamp as key\n\tnames := make(map[int64]string, rf.maxFiles+2)\n\n\t// Slice of timestamps later to be ordered\n\tkeys := make([]int64, 0, rf.maxFiles+2)\n\n\tfor _, file := range files {\n\t\tfn := file.Name()\n\n\t\t// Skip files that don't start with the prefix and end with the suffix.\n\t\tif !(strings.HasPrefix(fn, pfx) && strings.HasSuffix(fn, ext)) {\n\t\t\tcontinue\n\t\t}\n\t\t// Parse the timestamp from the file name\n\t\tvar ts time.Time\n\t\tif ts, err = time.Parse(rf.timeFormat, fn[len(pfx):len(fn)-len(ext)]); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tkey := ts.UnixNano()\n\t\tkeys = append(keys, key)\n\t\tnames[key] = fn\n\t}\n\tmx := int(rf.maxFiles) - 1 // -1 to account for the current log file\n\tif len(keys) <= mx {\n\t\treturn\n\t}\n\tsort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })\n\tfor _, key := range keys[:len(keys)-mx] {\n\t\t_ = os.Remove(filepath.Join(rf.dirName, names[key]))\n\t}\n}\n\nfunc (rf *RotatingFile) rotate() error {\n\tvar prevInfo SysInfo\n\tvar backupName string\n\tif rf.maxFiles == 0 || rf.maxFiles > 1 {\n\t\tvar err error\n\t\tprevInfo, err = FStat(rf.file)\n\t\tif err != nil || prevInfo == nil {\n\t\t\terr = fmt.Errorf(\"failed to stat %s: %w\", rf.file.Name(), err)\n\t\t\tclog.Error(rf.ctx, err)\n\t\t\treturn err\n\t\t}\n\n\t\tfullPath := filepath.Join(rf.dirName, rf.fileName)\n\t\tex := filepath.Ext(rf.fileName)\n\t\tsf := fullPath[:len(fullPath)-len(ex)]\n\t\tts := rf.fileTime(time.Now()).Format(rf.timeFormat)\n\t\tbackupName = fmt.Sprintf(\"%s-%s%s\", sf, ts, ex)\n\t}\n\terr := rf.openNew(prevInfo, backupName)\n\tif err != nil {\n\t\tclog.Error(rf.ctx, err)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/logging/rotatingfile_unix.go",
    "content": "//go:build !windows\n\npackage logging\n\nimport (\n\t\"time\"\n\n\t\"golang.org/x/term\"\n)\n\n// restoreCTimeAfterRename is a noop on unixes since the renamed file retains the creation time of the source.\nfunc restoreCTimeAfterRename(_ string, _ time.Time) error {\n\treturn nil\n}\n\n// IsTerminal returns whether the given file descriptor is a terminal.\nvar IsTerminal = term.IsTerminal //nolint:gochecknoglobals // os specific func replacement\n"
  },
  {
    "path": "pkg/client/logging/rotatingfile_windows.go",
    "content": "package logging\n\nimport (\n\t\"time\"\n\n\t\"golang.org/x/sys/windows\"\n)\n\n// IsTerminal returns whether the given file descriptor is a terminal.\nvar IsTerminal = func(fd int) bool { //nolint:gochecknoglobals // os specific func replacement\n\treturn false\n}\n\n// restoreCTimeAfterRename will restore the creation time on a file on Windows where\n// the file otherwise gets the creation time of the existing file that the operation\n// overwrites.\nfunc restoreCTimeAfterRename(path string, ctime time.Time) error {\n\tp16, e := windows.UTF16PtrFromString(path)\n\tif e != nil {\n\t\treturn e\n\t}\n\th, e := windows.CreateFile(p16,\n\t\twindows.FILE_WRITE_ATTRIBUTES, windows.FILE_SHARE_WRITE, nil,\n\t\twindows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS, 0)\n\tif e != nil {\n\t\treturn e\n\t}\n\tdefer windows.Close(h)\n\tc := windows.NsecToFiletime(ctime.UnixNano())\n\treturn windows.SetFileTime(h, &c, nil, nil)\n}\n"
  },
  {
    "path": "pkg/client/logging/stat.go",
    "content": "package logging\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\n// FStat returns the file status/info of an open file.\nfunc FStat(file dos.File) (SysInfo, error) {\n\treturn osFStat(file)\n}\n\n// SysInfo represents the elaborate info in a FileInfo.Sys(). The implementations are\n// os specific.\n//\n// Unix:\n//\n//\tinfo.Sys().(*syscall.Stat_t)\n//\n// Windows:\n//\n//\tinfo.Sys().(*syscall.Win32FileAttributeData)\ntype SysInfo interface {\n\tfmt.Stringer\n\n\tSize() int64\n\n\tBirthTime() time.Time\n\tModifyTime() time.Time // most recent content change\n\tChangeTime() time.Time // most recent metadata change\n\n\tSetOwnerAndGroup(name string) error\n\n\tHaveSameOwnerAndGroup(SysInfo) bool\n}\n"
  },
  {
    "path": "pkg/client/logging/stat_darwin.go",
    "content": "package logging\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"syscall\" //nolint:depguard // We specifically need \"syscall.Stat_t\" rather than \"unix.Stat_t\" for fs.File.Sys().\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\ntype fileInfo struct {\n\t*syscall.Stat_t\n}\n\nfunc osFStat(file dos.File) (SysInfo, error) {\n\tstat, err := file.Stat()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to stat %s: %w\", file.Name(), err)\n\t}\n\tsys, ok := stat.Sys().(*syscall.Stat_t)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"files of type %T don't support Fstat\", file)\n\t}\n\treturn fileInfo{sys}, nil\n}\n\nfunc (u fileInfo) Size() int64 {\n\treturn u.Stat_t.Size\n}\n\nfunc (u fileInfo) SetOwnerAndGroup(name string) error {\n\treturn os.Chown(name, int(u.Uid), int(u.Gid))\n}\n\nfunc (u fileInfo) HaveSameOwnerAndGroup(other SysInfo) bool {\n\tou := other.(fileInfo)\n\treturn u.Uid == ou.Uid && u.Gid == ou.Gid\n}\n\nfunc (u fileInfo) String() string {\n\treturn fmt.Sprintf(\"CTIME %v, UID %d, GID %d\", u.BirthTime(), u.Uid, u.Gid)\n}\n\nfunc (u fileInfo) BirthTime() time.Time  { return time.Unix(u.Birthtimespec.Unix()) }\nfunc (u fileInfo) ModifyTime() time.Time { return time.Unix(u.Mtimespec.Unix()) }\nfunc (u fileInfo) ChangeTime() time.Time { return time.Unix(u.Ctimespec.Unix()) }\n"
  },
  {
    "path": "pkg/client/logging/stat_linux.go",
    "content": "package logging\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\ntype statable interface {\n\tFd() uintptr\n\tName() string\n}\n\ntype fileInfo struct {\n\tsize  int64\n\tuid   int\n\tgid   int\n\tbtime time.Time\n\tmtime time.Time\n\tctime time.Time\n}\n\nfunc osFStat(dfile dos.File) (SysInfo, error) {\n\tfile, ok := dfile.(statable)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"files of type %T don't support Fstat\", dfile)\n\t}\n\tconst want = 0 |\n\t\tunix.STATX_SIZE |\n\t\tunix.STATX_UID |\n\t\tunix.STATX_GID |\n\t\tunix.STATX_BTIME |\n\t\tunix.STATX_MTIME |\n\t\tunix.STATX_CTIME\n\n\tvar stat unix.Statx_t\n\tif err := unix.Statx(int(file.Fd()), \"\", unix.AT_EMPTY_PATH, want, &stat); err != nil {\n\t\tif errors.Is(err, unix.ENOSYS) {\n\t\t\t// The statx(2) system call was introduced in Linux 4.11 (2017).  That's new\n\t\t\t// enough that we should have a fallback.\n\t\t\treturn oldFStat(file)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to statx %s: %w\", file.Name(), err)\n\t}\n\n\tif stat.Mask&want != want {\n\t\t// Not all filesystems (notably: tmpfs) support btime.\n\t\treturn oldFStat(file)\n\t}\n\n\treturn fileInfo{\n\t\tsize:  int64(stat.Size),\n\t\tuid:   int(stat.Uid),\n\t\tgid:   int(stat.Gid),\n\t\tbtime: time.Unix(stat.Btime.Sec, int64(stat.Btime.Nsec)),\n\t\tmtime: time.Unix(stat.Mtime.Sec, int64(stat.Mtime.Nsec)),\n\t\tctime: time.Unix(stat.Ctime.Sec, int64(stat.Ctime.Nsec)),\n\t}, nil\n}\n\nfunc oldFStat(file statable) (SysInfo, error) {\n\tvar stat unix.Stat_t\n\tif err := unix.Fstat(int(file.Fd()), &stat); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to stat %s: %w\", file.Name(), err)\n\t}\n\treturn fileInfo{\n\t\tsize: stat.Size,\n\t\tuid:  int(stat.Uid),\n\t\tgid:  int(stat.Gid),\n\t\t// The reason we wanted statx(2) in the first place is\n\t\t// because fstat(2) doesn't give us the birthtime.  Fake it\n\t\t// with the changetime.  I'm not sure why changetime is the\n\t\t// best choice, but it's what Telepresence did before we\n\t\t// added statx support.\n\t\tbtime: time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec),\n\t\tmtime: time.Unix(stat.Mtim.Sec, stat.Mtim.Nsec),\n\t\tctime: time.Unix(stat.Ctim.Sec, stat.Ctim.Nsec),\n\t}, nil\n}\n\nfunc (u fileInfo) Size() int64 {\n\treturn u.size\n}\n\nfunc (u fileInfo) SetOwnerAndGroup(name string) error {\n\treturn os.Chown(name, u.uid, u.gid)\n}\n\nfunc (u fileInfo) HaveSameOwnerAndGroup(other SysInfo) bool {\n\tou := other.(fileInfo)\n\treturn u.uid == ou.uid && u.gid == ou.gid\n}\n\nfunc (u fileInfo) String() string {\n\treturn fmt.Sprintf(\"BTIME %v, MTIME %v, CTIME %v, UID %d, GID %d\",\n\t\tu.btime, u.mtime, u.ctime, u.uid, u.gid)\n}\n\nfunc (u fileInfo) BirthTime() time.Time  { return u.btime }\nfunc (u fileInfo) ModifyTime() time.Time { return u.mtime }\nfunc (u fileInfo) ChangeTime() time.Time { return u.ctime }\n"
  },
  {
    "path": "pkg/client/logging/stat_linux_test.go",
    "content": "package logging_test\n\nimport (\n\t\"errors\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc init() {\n\tvar stat unix.Statx_t\n\terr := unix.Statx(-1, \"/\", 0, unix.STATX_BTIME, &stat)\n\tif err != nil && errors.Is(err, unix.ENOSYS) {\n\t\tosHasBTime = false\n\t}\n}\n"
  },
  {
    "path": "pkg/client/logging/stat_test.go",
    "content": "package logging_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\nvar osHasBTime = true\n\nfunc TestFStat(t *testing.T) {\n\tbtimeIsCTime := testFStat(t, runtime.GOOS == \"linux\")\n\tif btimeIsCTime && osHasBTime {\n\t\t// The kernel supports btime, but the filesystem doesn't.  Set TMPDIR to be\n\t\t// $HOME/tmp, on the assumption that $HOME is on a \"big boy\" filesystem and thus\n\t\t// supports btime.\n\t\tt.Run(\"tmpdirInHome\", func(t *testing.T) {\n\t\t\tos.Setenv(\"TMPDIR\", filepath.Join(os.Getenv(\"HOME\"), \"tmp\"))\n\t\t\terr := os.Mkdir(os.Getenv(\"TMPDIR\"), 0o777)\n\t\t\tif err != nil && !errors.Is(err, os.ErrExist) {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\ttestFStat(t, false)\n\t\t})\n\t}\n}\n\nfunc testFStat(t *testing.T, okIfBTimeIsCTime bool) (btimeIsCTime bool) {\n\tconst (\n\t\tfsVsClockLeeway = 1 * time.Second // many filesystems only have second precision\n\t\tminDelta        = 2 * time.Second\n\t)\n\n\tctx := context.Background()\n\tctx = client.WithEnv(ctx, &client.Env{})\n\tfilename := filepath.Join(t.TempDir(), \"stamp.txt\")\n\twithFile := func(flags int, fn func(dos.File)) (time.Time, time.Time) {\n\t\tbefore := time.Now()\n\t\ttime.Sleep(fsVsClockLeeway)\n\t\tfile, err := dos.OpenFile(ctx, filename, flags, 0o666)\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, file)\n\t\tfn(file)\n\t\trequire.NoError(t, file.Close())\n\t\ttime.Sleep(fsVsClockLeeway)\n\t\tafter := time.Now()\n\t\treturn before, after\n\t}\n\n\t// btime\n\tbBefore, bAfter := withFile(os.O_CREATE|os.O_RDWR, func(file dos.File) {})\n\n\ttime.Sleep(minDelta)\n\n\t// mtime\n\tmBefore, mAfter := withFile(os.O_RDWR, func(file dos.File) {\n\t\t_, err := io.WriteString(file, \"#!/bin/sh\\n\")\n\t\trequire.NoError(t, err)\n\t})\n\n\ttime.Sleep(minDelta)\n\n\t// ctime\n\tcBefore := time.Now()\n\ttime.Sleep(fsVsClockLeeway)\n\trequire.NoError(t, os.Chmod(filename, 0o777))\n\ttime.Sleep(fsVsClockLeeway)\n\tcAfter := time.Now()\n\n\t// stat\n\tvar stat logging.SysInfo\n\twithFile(os.O_RDWR, func(file dos.File) {\n\t\tvar err error\n\t\tstat, err = logging.FStat(file)\n\t\trequire.NoError(t, err)\n\t})\n\n\t// validate\n\tassertInRange := func(before, after, x time.Time, msg string) {\n\t\tif x.Before(before) || x.After(after) {\n\t\t\tt.Errorf(\"%s: %v: not in range [%v, %v]\", msg, x, before, after)\n\t\t} else {\n\t\t\tt.Logf(\"%s: %v\", msg, x)\n\t\t}\n\t}\n\n\tif okIfBTimeIsCTime && stat.BirthTime().Equal(stat.ChangeTime()) {\n\t\tbtimeIsCTime = true\n\t\tt.Logf(\"btime: %v (spoofed with ctime)\", stat.BirthTime())\n\t} else {\n\t\tassertInRange(bBefore, bAfter, stat.BirthTime(), \"btime\")\n\t}\n\tassertInRange(mBefore, mAfter, stat.ModifyTime(), \"mtime\")\n\tif runtime.GOOS == \"windows\" {\n\t\tcBefore, cAfter = mBefore, mAfter\n\t}\n\tassertInRange(cBefore, cAfter, stat.ChangeTime(), \"ctime\")\n\treturn btimeIsCTime\n}\n"
  },
  {
    "path": "pkg/client/logging/stat_windows.go",
    "content": "package logging\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"syscall\" //nolint:depguard // We specifically need \"syscall.Win32FileAttributeData\" rather than \"windows.Win32FileAttributeData\" for fs.File.Sys().\n\t\"time\"\n\n\t\"github.com/hectane/go-acl/api\"\n\t\"golang.org/x/sys/windows\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\ntype WindowsSysInfo interface {\n\tSysInfo\n\tOwner() *windows.SID\n\tGroup() *windows.SID\n\tDACL() windows.Handle\n\tSACL() windows.Handle\n\tSecurityDescriptor() windows.Handle\n}\n\ntype windowsSysInfo struct {\n\tpath    string\n\tdata    *syscall.Win32FileAttributeData\n\towner   *windows.SID\n\tgroup   *windows.SID\n\tdacl    windows.Handle\n\tsacl    windows.Handle\n\tsecDesc windows.Handle\n}\n\nfunc osFStat(file dos.File) (SysInfo, error) {\n\tinfo, err := file.Stat()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to stat %s: %w\", file.Name(), err)\n\t}\n\tsys, ok := info.Sys().(*syscall.Win32FileAttributeData)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"files of type %T don't support Fstat\", file)\n\t}\n\twi := windowsSysInfo{\n\t\tpath: file.Name(),\n\t\tdata: sys,\n\t}\n\terr = api.GetNamedSecurityInfo(\n\t\twi.path,\n\t\tapi.SE_FILE_OBJECT,\n\t\tapi.OWNER_SECURITY_INFORMATION,\n\t\t&wi.owner,\n\t\t&wi.group,\n\t\t&wi.dacl,\n\t\t&wi.sacl,\n\t\t&wi.secDesc,\n\t)\n\tif err != nil && !errors.Is(err, windows.ERROR_SUCCESS) {\n\t\treturn nil, err\n\t}\n\truntime.SetFinalizer(&wi, func(wi *windowsSysInfo) {\n\t\t_, _ = windows.LocalFree(wi.secDesc)\n\t})\n\treturn &wi, nil\n}\n\nfunc (wi *windowsSysInfo) Size() int64 {\n\treturn int64(wi.data.FileSizeHigh)<<32 + int64(wi.data.FileSizeLow)\n}\n\nfunc (wi *windowsSysInfo) SetOwnerAndGroup(name string) error {\n\terr := api.SetNamedSecurityInfo(name, api.SE_FILE_OBJECT, api.OWNER_SECURITY_INFORMATION, wi.owner, wi.group, wi.dacl, wi.sacl)\n\tif err != nil {\n\t\t// On some systems it seems SetNamedSecurityInfo will return ERROR_SUCCESS on success... this is an odd violation of the principle\n\t\t// that windows APIs return err = nil on success but okay\n\t\tif errors.Is(err, windows.ERROR_SUCCESS) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (wi *windowsSysInfo) HaveSameOwnerAndGroup(s SysInfo) bool {\n\teq := func(a, b *windows.SID) bool {\n\t\tif a == b {\n\t\t\treturn true\n\t\t}\n\t\tif a == nil || b == nil {\n\t\t\treturn false\n\t\t}\n\t\tif a.IsValid() {\n\t\t\tif b.IsValid() {\n\t\t\t\treturn a.Equals(b)\n\t\t\t}\n\t\t\treturn false\n\t\t}\n\t\treturn !b.IsValid()\n\t}\n\towi, ok := s.(*windowsSysInfo)\n\treturn ok && eq(wi.owner, owi.owner) && eq(wi.group, owi.group)\n}\n\nfunc (wi *windowsSysInfo) BirthTime() time.Time {\n\treturn time.Unix(0, wi.data.CreationTime.Nanoseconds())\n}\n\nfunc (wi *windowsSysInfo) ModifyTime() time.Time {\n\treturn time.Unix(0, wi.data.LastWriteTime.Nanoseconds())\n}\n\nfunc (wi *windowsSysInfo) ChangeTime() time.Time {\n\treturn time.Unix(0, wi.data.LastWriteTime.Nanoseconds())\n}\n\nfunc (wi *windowsSysInfo) Owner() *windows.SID {\n\treturn wi.owner\n}\n\nfunc (wi *windowsSysInfo) Group() *windows.SID {\n\treturn wi.group\n}\n\nfunc (wi *windowsSysInfo) DACL() windows.Handle {\n\treturn wi.dacl\n}\n\nfunc (wi *windowsSysInfo) SACL() windows.Handle {\n\treturn wi.sacl\n}\n\nfunc (wi *windowsSysInfo) String() string {\n\tov := \"invalid\"\n\tif wi.owner != nil && wi.owner.IsValid() {\n\t\tov = wi.owner.String()\n\t}\n\tgv := \"invalid\"\n\tif wi.group != nil && wi.group.IsValid() {\n\t\tgv = wi.group.String()\n\t}\n\treturn fmt.Sprintf(\"CTIME %v, UID %v, GID %v\", wi.BirthTime(), ov, gv)\n}\n"
  },
  {
    "path": "pkg/client/portforward/borrowed_kubectl_cmdutil.go",
    "content": "/*\nMODIFIED: This file is a verbatim subset of kubectl v1.21.2 pkg/cmd/util/kubectl_match_version.go,\nMODIFIED: except for lines marked \"MODIFIED\".\n\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 portforward // MODIFIED\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/kubectl/pkg/scheme\"\n)\n\n// setKubernetesDefaults sets default values on the provided client config for accessing the\n// Kubernetes API or returns an error if any of the defaults are impossible or invalid.\n// TODO this isn't what we want.  Each clientset should be setting defaults as it sees fit.\nfunc setKubernetesDefaults(config *rest.Config) error {\n\t// TODO remove this hack.  This is allowing the GetOptions to be serialized.\n\tconfig.GroupVersion = &schema.GroupVersion{Group: \"\", Version: \"v1\"}\n\n\tif config.APIPath == \"\" {\n\t\tconfig.APIPath = \"/api\"\n\t}\n\tif config.NegotiatedSerializer == nil {\n\t\t// This codec factory ensures the resources are not converted. Therefore, resources\n\t\t// will not be round-tripped through internal versions. Defaulting does not happen\n\t\t// on the client.\n\t\tconfig.NegotiatedSerializer = scheme.Codecs.WithoutConversion()\n\t}\n\treturn rest.SetKubernetesDefaults(config)\n}\n"
  },
  {
    "path": "pkg/client/portforward/grpcresolver.go",
    "content": "package portforward\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"google.golang.org/grpc/resolver\"\n\t\"google.golang.org/grpc/serviceconfig\"\n)\n\nconst (\n\tK8sPFScheme = \"k8spf\"\n)\n\ntype resolverBuilder struct {\n\tcontext.Context\n\tknownPod *PodAddress\n}\n\nfunc NewResolver(ctx context.Context, knownPod *PodAddress) resolver.Builder {\n\treturn resolverBuilder{Context: ctx, knownPod: knownPod}\n}\n\nfunc (p resolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (rs resolver.Resolver, err error) {\n\tif target.URL.Host != \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid (non-empty) authority: %v\", target.URL.Host)\n\t}\n\tif target.URL.Scheme != K8sPFScheme {\n\t\treturn nil, fmt.Errorf(\"invalid scheme: %v\", target.URL.Scheme)\n\t}\n\tif strings.HasPrefix(target.Endpoint(), \"svc/\") {\n\t\tctx, cancel := context.WithCancel(p.Context)\n\t\trs := &svcResolver{\n\t\t\tctx:      ctx,\n\t\t\tcancel:   cancel,\n\t\t\tcc:       cc,\n\t\t\trn:       make(chan struct{}),\n\t\t\tendPoint: target.Endpoint(),\n\t\t\tlastPA:   p.knownPod,\n\t\t}\n\t\trs.wg.Add(1)\n\t\tgo rs.watcher()\n\t\treturn rs, nil\n\t}\n\n\tvar state resolver.State\n\tpa, err := resolve(p.Context, target.Endpoint())\n\tif err == nil {\n\t\tstate = pa.state()\n\t} else {\n\t\tstate = resolver.State{ServiceConfig: &serviceconfig.ParseResult{Err: err}}\n\t}\n\treturn &noopResolver{}, cc.UpdateState(state)\n}\n\nfunc (p resolverBuilder) Scheme() string {\n\treturn K8sPFScheme\n}\n\ntype noopResolver struct{}\n\nfunc (noopResolver) ResolveNow(_ resolver.ResolveNowOptions) {}\n\nfunc (noopResolver) Close() {}\n\ntype svcResolver struct {\n\tctx      context.Context\n\tcancel   context.CancelFunc\n\tendPoint string\n\tcc       resolver.ClientConn\n\twg       sync.WaitGroup\n\trn       chan struct{}\n\tlastPA   *PodAddress\n}\n\n// ResolveNow invoke an immediate resolution of the target that this\n// dnsResolver watches.\nfunc (d *svcResolver) ResolveNow(resolver.ResolveNowOptions) {\n\tselect {\n\tcase d.rn <- struct{}{}:\n\tdefault:\n\t}\n}\n\nfunc (d *svcResolver) Close() {\n\td.cancel()\n\td.wg.Wait()\n}\n\nfunc (d *svcResolver) watcher() {\n\tdefer d.wg.Done()\n\tif d.lastPA != nil {\n\t\terr := d.cc.UpdateState(d.lastPA.state())\n\t\tif err == nil {\n\t\t\t// Wait for next ResolveNow\n\t\t\tselect {\n\t\t\tcase <-d.ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-d.rn:\n\t\t\t}\n\t\t}\n\t}\n\tebo := backoff.NewExponentialBackOff(\n\t\tbackoff.WithInitialInterval(2*time.Second),\n\t\tbackoff.WithMaxInterval(7*time.Second),\n\t\tbackoff.WithMaxElapsedTime(120*time.Second),\n\t)\n\tfor {\n\t\tpa, err := resolve(d.ctx, d.endPoint)\n\t\tif err != nil {\n\t\t\t// Report error to the underlying grpc.ClientConn.\n\t\t\td.cc.ReportError(err)\n\t\t} else if d.lastPA == nil || *pa != *d.lastPA {\n\t\t\terr = d.cc.UpdateState(pa.state())\n\t\t}\n\n\t\tif err == nil {\n\t\t\t// Success resolving, wait for the next ResolveNow.\n\t\t\td.lastPA = pa\n\t\t\tselect {\n\t\t\tcase <-d.ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-d.rn:\n\t\t\t\tebo.Reset()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t}\n\t\tselect {\n\t\tcase <-d.ctx.Done():\n\t\t\treturn\n\t\tcase <-time.After(ebo.NextBackOff()):\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/portforward/podaddr.go",
    "content": "package portforward\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/google/uuid\"\n\t\"google.golang.org/grpc/resolver\"\n\tk8sTypes \"k8s.io/apimachinery/pkg/types\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype PodAddress struct {\n\tFromSvc   bool\n\tName      string\n\tNamespace string\n\tPort      uint16\n\tProto     types.Proto\n\tPodID     k8sTypes.UID\n}\n\nfunc parseAddr(fullAddr string) (kind, name, namespace, port string, podID k8sTypes.UID, err error) {\n\taddr := fullAddr\n\tif hash := strings.LastIndex(fullAddr, \"#\"); hash > 0 {\n\t\tid := addr[hash+1:]\n\t\taddr = addr[:hash]\n\t\tif _, err := uuid.Parse(id); err == nil {\n\t\t\tpodID = k8sTypes.UID(id)\n\t\t}\n\t}\n\tif slash := strings.Index(addr, \"/\"); slash < 0 {\n\t\tkind = \"pod\"\n\t} else {\n\t\tkind = addr[:slash]\n\t\taddr = addr[slash+1:]\n\t}\n\tif name, port, err = net.SplitHostPort(addr); err == nil {\n\t\tvar namespace string\n\t\tif dot := strings.LastIndex(name, \".\"); dot > 0 {\n\t\t\tnamespace = name[dot+1:]\n\t\t\tname = name[:dot]\n\t\t}\n\t\treturn kind, name, namespace, port, podID, nil\n\t}\n\treturn \"\", \"\", \"\", \"\", \"\", fmt.Errorf(\"%q is not a valid [<kind>/]<name[.namespace]>:<port-number>[#<uid>]\", fullAddr)\n}\n\nfunc parsePodAddr(addr string) (PodAddress, error) {\n\tkind, name, namespace, port, podId, err := parseAddr(addr)\n\tif err != nil {\n\t\treturn PodAddress{}, err\n\t}\n\tif kind == \"pod\" {\n\t\tif pn, err := strconv.ParseUint(port, 10, 16); err == nil {\n\t\t\treturn PodAddress{\n\t\t\t\tName:      name,\n\t\t\t\tNamespace: namespace,\n\t\t\t\tPort:      uint16(pn),\n\t\t\t\tPodID:     podId,\n\t\t\t}, nil\n\t\t}\n\t}\n\treturn PodAddress{}, fmt.Errorf(\"%q is not a valid pod port address\", addr)\n}\n\nfunc (pa *PodAddress) String() string {\n\treturn fmt.Sprintf(\"%s.%s:%d#%s\", pa.Name, pa.Namespace, pa.Port, pa.PodID)\n}\n\nfunc (pa *PodAddress) state() resolver.State {\n\treturn resolver.State{Addresses: []resolver.Address{{Addr: pa.String()}}}\n}\n"
  },
  {
    "path": "pkg/client/portforward/resolve.go",
    "content": "package portforward\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sort\"\n\t\"strconv\"\n\t\"time\"\n\n\tcore \"k8s.io/api/core/v1\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\twatchtools \"k8s.io/client-go/tools/watch\"\n\t\"k8s.io/kubectl/pkg/polymorphichelpers\"\n\t\"k8s.io/kubectl/pkg/util/podutils\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nfunc ResolveServiceAndPort(ctx context.Context, name, namespace string, portName string, proto types.Proto) (pap types.AddrPortProto, err error) {\n\tif pn, err := strconv.Atoi(name); err == nil {\n\t\tif ip, err := netip.ParseAddr(name); err == nil {\n\t\t\treturn types.AddrPortProto{\n\t\t\t\tAddrPort: netip.AddrPortFrom(ip, uint16(pn)),\n\t\t\t\tProto:    proto,\n\t\t\t}, nil\n\t\t}\n\t}\n\tsvcObj, err := k8sapi.GetService(ctx, name, namespace)\n\tif err != nil {\n\t\treturn pap, err\n\t}\n\tsvc, _ := k8sapi.ServiceImpl(svcObj)\n\tif svc.Spec.ClusterIP == core.ClusterIPNone {\n\t\treturn pap, fmt.Errorf(\"service '%s' is not accessible from outside the cluster\", name)\n\t}\n\tip, err := netip.ParseAddr(svc.Spec.ClusterIP)\n\tif err != nil {\n\t\treturn pap, fmt.Errorf(\"unable to parse ClusterIP %q of service '%s': %v\", svc.Spec.ClusterIP, name, err)\n\t}\n\tsvcPort, err := servicePortByName(svc, portName, core.Protocol(proto.String()))\n\tif err != nil {\n\t\treturn pap, err\n\t}\n\treturn types.AddrPortProto{\n\t\tAddrPort: netip.AddrPortFrom(ip, uint16(svcPort.Port)),\n\t\tProto:    types.FromK8sProtocol(svcPort.Protocol),\n\t}, nil\n}\n\nfunc ResolveSvcToPod(ctx context.Context, name, namespace, portName string) (pa *PodAddress, err error) {\n\t// Get the service.\n\tpa = new(PodAddress)\n\tpa.FromSvc = true\n\tpa.Namespace = namespace\n\tsvcObj, err := k8sapi.GetService(ctx, name, namespace)\n\tif err != nil {\n\t\treturn pa, err\n\t}\n\tsvc, _ := k8sapi.ServiceImpl(svcObj)\n\tsvcPort, err := servicePortByName(svc, portName, \"\")\n\tif err != nil {\n\t\treturn pa, err\n\t}\n\n\t// Resolve the Service to a Pod.\n\tvar selector labels.Selector\n\tvar podNS string\n\tpodNS, selector, err = polymorphichelpers.SelectorsForObject(svc)\n\tif err != nil {\n\t\treturn pa, fmt.Errorf(\"cannot attach to %T: %v\", svc, err)\n\t}\n\ttimeout := func() time.Duration {\n\t\tif deadline, ok := ctx.Deadline(); ok {\n\t\t\treturn time.Until(deadline)\n\t\t}\n\t\t// Fall back to the same default as --pod-running-timeout.\n\t\treturn time.Minute\n\t}()\n\n\tsortBy := func(pods []*core.Pod) sort.Interface { return sort.Reverse(podutils.ActivePods(pods)) }\n\tpodList, err := getPods(ctx, podNS, selector.String(), timeout, sortBy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, p := range podList {\n\t\tif p.Status.Phase == core.PodRunning {\n\t\t\tcontainerPort, err := containerPortNumber(p, svcPort.TargetPort)\n\t\t\tif err == nil {\n\t\t\t\tpa.Name = p.Name\n\t\t\t\tpa.Port = containerPort\n\t\t\t\tpa.Proto = types.FromK8sProtocol(svcPort.Protocol)\n\t\t\t\tpa.PodID = p.UID\n\t\t\t\treturn pa, nil\n\t\t\t}\n\t\t}\n\t}\n\treturn pa, fmt.Errorf(\"no running pods with accessible ports found for service %s.%s\", name, namespace)\n}\n\nfunc servicePortByName(svc *core.Service, name string, proto core.Protocol) (*core.ServicePort, error) {\n\tsps := svc.Spec.Ports\n\tif proto == \"\" {\n\t\tproto = core.ProtocolTCP\n\t}\n\tif pn, err := strconv.Atoi(name); err == nil {\n\t\tfor si := range sps {\n\t\t\tsp := &sps[si]\n\t\t\tif sp.Port == int32(pn) && (proto == sp.Protocol || proto == core.ProtocolTCP && sp.Protocol == \"\") {\n\t\t\t\treturn sp, nil\n\t\t\t}\n\t\t}\n\t\treturn nil, fmt.Errorf(\"service '%s' does not have %s port number '%d'\", svc.Name, proto, pn)\n\t}\n\tfor si := range sps {\n\t\tsp := &sps[si]\n\t\tif sp.Name == name && (proto == sp.Protocol || proto == core.ProtocolTCP && sp.Protocol == \"\") {\n\t\t\treturn sp, nil\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"service '%s' does not have a %s port named '%s'\", svc.Name, proto, name)\n}\n\nfunc containerPortNumber(pod *core.Pod, port intstr.IntOrString) (uint16, error) {\n\tif port.Type == intstr.Int {\n\t\t// It's not required for the container to declare the port.\n\t\treturn uint16(port.IntVal), nil\n\t}\n\tname := port.StrVal\n\tcns := pod.Spec.Containers\n\tfor ci := range cns {\n\t\tcn := &cns[ci]\n\t\tfor pi := range cn.Ports {\n\t\t\tcp := &cn.Ports[pi]\n\t\t\tif cp.Name == name {\n\t\t\t\treturn uint16(cp.ContainerPort), nil\n\t\t\t}\n\t\t}\n\t}\n\treturn 0, fmt.Errorf(\"pod '%s' does not have a port named '%s'\", pod.Name, name)\n}\n\nfunc resolve(ctx context.Context, addr string) (pa *PodAddress, err error) {\n\tkind, name, namespace, port, podID, err := parseAddr(addr)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"cannot resolve addr %s: %v\", addr, err)\n\t\treturn nil, err\n\t}\n\n\tif kind == \"svc\" {\n\t\t// Get the service.\n\t\treturn ResolveSvcToPod(ctx, name, namespace, port)\n\t}\n\n\tvar pn uint16\n\tif p, err := strconv.ParseUint(port, 10, 16); err == nil {\n\t\tpn = uint16(p)\n\t}\n\tif pn != 0 && podID != \"\" {\n\t\treturn &PodAddress{Name: name, Namespace: namespace, Port: pn, PodID: podID}, nil\n\t}\n\n\t// Get the pod.\n\tpodObj, err := k8sapi.GetPod(ctx, name, namespace)\n\tif err != nil {\n\t\treturn pa, fmt.Errorf(\"unable to get %s %s.%s: %w\", kind, name, namespace, err)\n\t}\n\tpod, _ := k8sapi.PodImpl(podObj)\n\tif pn == 0 {\n\t\tpn, err = containerPortNumber(pod, intstr.Parse(port))\n\t\tif err != nil {\n\t\t\treturn pa, err\n\t\t}\n\t}\n\treturn &PodAddress{\n\t\tName:      pod.Name,\n\t\tNamespace: pod.Namespace,\n\t\tPort:      pn,\n\t\tPodID:     pod.UID,\n\t}, nil\n}\n\n// getPods returns a PodList matching the namespace and label selector.\nfunc getPods(ctx context.Context, namespace string, selector string, timeout time.Duration, sortBy func([]*core.Pod) sort.Interface) ([]*core.Pod, error) {\n\toptions := meta.ListOptions{LabelSelector: selector}\n\n\tclient := k8sapi.GetK8sInterface(ctx).CoreV1()\n\tpodList, err := client.Pods(namespace).List(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tps := podList.Items\n\tif len(ps) > 0 {\n\t\tpods := make([]*core.Pod, len(ps))\n\t\tfor i := range ps {\n\t\t\tpods[i] = &ps[i]\n\t\t}\n\t\tsort.Sort(sortBy(pods))\n\t\treturn pods, nil\n\t}\n\n\t// Watch until we observe a pod\n\toptions.ResourceVersion = podList.ResourceVersion\n\tw, err := client.Pods(namespace).Watch(ctx, options)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer w.Stop()\n\n\tcondition := func(event watch.Event) (bool, error) {\n\t\treturn event.Type == watch.Added || event.Type == watch.Modified, nil\n\t}\n\tif timeout != 0 {\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithTimeout(ctx, timeout)\n\t\tdefer cancel()\n\t}\n\n\tevent, err := watchtools.UntilWithoutRetry(ctx, w, condition)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tpo, ok := event.Object.(*core.Pod)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"%#v is not a pod event\", event)\n\t}\n\treturn []*core.Pod{po}, nil\n}\n"
  },
  {
    "path": "pkg/client/portforward/streamconn.go",
    "content": "package portforward\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\tcore \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/apimachinery/pkg/util/httpstream\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/portforward\"\n\t\"k8s.io/client-go/transport/spdy\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\nconst ProtocolV1Name = \"portforward.k8s.io\"\n\ntype podDialer struct {\n\tstreamConn httpstream.Connection\n\trequestID  int64\n\trefCount   int64\n\tonClose    func()\n}\n\ntype dialerKey struct{}\n\ntype config struct {\n\tpodDialers *xsync.Map[types.UID, *podDialer]\n\trestConfig *rest.Config\n}\n\nfunc WithRestConfig(ctx context.Context, restConfig *rest.Config) context.Context {\n\treturn context.WithValue(ctx, dialerKey{}, &config{\n\t\tpodDialers: xsync.NewMap[types.UID, *podDialer](),\n\t\trestConfig: restConfig,\n\t})\n}\n\nfunc Dialer(ctx context.Context) func(ctx context.Context, address string) (net.Conn, error) {\n\tcfg, ok := ctx.Value(dialerKey{}).(*config)\n\treturn func(grpcCtx context.Context, address string) (net.Conn, error) {\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"grpc dialer is not configured\")\n\t\t}\n\t\tclog.Debugf(ctx, \"portforward.Dialer dialing %s\", address)\n\t\treturn dialContext(grpcCtx, ctx, address, cfg)\n\t}\n}\n\nfunc dialContext(grpcCtx, logCtx context.Context, addr string, cfg *config) (net.Conn, error) {\n\tpa, err := parsePodAddr(addr)\n\tif err != nil {\n\t\tclog.Error(logCtx, err)\n\t\treturn nil, err\n\t}\n\tkey := pa.PodID\n\tif key == \"\" {\n\t\terr = errors.New(\"pod ID is empty\")\n\t\tclog.Error(logCtx, err)\n\t\treturn nil, err\n\t}\n\tpc, _ := cfg.podDialers.LoadOrCompute(key, func() (pc *podDialer, cancel bool) {\n\t\tpc, err = newPodDialer(logCtx, key, cfg, pa.Name, pa.Namespace)\n\t\treturn pc, err != nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn pc.dial(grpcCtx, pa.Port)\n}\n\nfunc newPodDialer(ctx context.Context, key types.UID, cfg *config, name, namespace string) (*podDialer, error) {\n\tsd, err := newStreamDialer(ctx, cfg.restConfig, name, namespace, client.GetConfig(ctx).Cluster().ForceSPDY)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstreamConn, protocol, err := sd.Dial(ProtocolV1Name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error upgrading connection: %s\", err)\n\t}\n\tif protocol != ProtocolV1Name {\n\t\treturn nil, fmt.Errorf(\"unable to negotiate protocol: client supports %q, server returned %q\", ProtocolV1Name, protocol)\n\t}\n\treturn &podDialer{streamConn: streamConn, onClose: func() {\n\t\tcfg.podDialers.Delete(key)\n\t}}, nil\n}\n\nfunc newStreamDialer(ctx context.Context, config *rest.Config, podName, namespace string, forceSPDY bool) (httpstream.Dialer, error) {\n\terr := setKubernetesDefaults(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trc, err := rest.RESTClientFor(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\turl := rc.\n\t\tPost().\n\t\tResource(\"pods\").\n\t\tNamespace(namespace).\n\t\tName(podName).\n\t\tSubResource(\"portforward\").\n\t\tURL()\n\n\ttransport, upgrader, err := spdy.RoundTripperFor(config)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, \"POST\", url)\n\tif !forceSPDY {\n\t\tclog.Debugf(ctx, \"Using WebSocket based port-forward to pod %s.%s\", podName, namespace)\n\t\ttunnelingDialer, err := portforward.NewSPDYOverWebsocketDialer(url, config)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// First attempt tunneling (websocket) dialer, then fallback to spdy dialer.\n\t\tdialer = portforward.NewFallbackDialer(tunnelingDialer, dialer, func(err error) bool {\n\t\t\treturn httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err)\n\t\t})\n\t} else {\n\t\tclog.Debugf(ctx, \"Using SPDY based port-forward to pod %s.%s\", podName, namespace)\n\t}\n\treturn dialer, nil\n}\n\nfunc (pc *podDialer) dial(ctx context.Context, remotePort uint16) (conn net.Conn, err error) {\n\tatomic.AddInt64(&pc.refCount, 1)\n\tvar dataStream, errorStream httpstream.Stream\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tatomic.AddInt64(&pc.refCount, -1)\n\t\t\tif errorStream != nil {\n\t\t\t\terrorStream.Close()\n\t\t\t\tpc.streamConn.RemoveStreams(errorStream)\n\t\t\t}\n\t\t}\n\t}()\n\n\trequestID := atomic.AddInt64(&pc.requestID, 1)\n\t// create error stream\n\theaders := http.Header{}\n\theaders.Set(core.StreamType, core.StreamTypeError)\n\theaders.Set(core.PortHeader, strconv.Itoa(int(remotePort)))\n\theaders.Set(core.PortForwardRequestIDHeader, strconv.Itoa(int(requestID)))\n\terrorStream, err = pc.streamConn.CreateStream(headers)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating error stream for port %d: %v\", remotePort, err)\n\t}\n\n\tgo func() {\n\t\tmessage, err := io.ReadAll(errorStream)\n\t\tswitch {\n\t\tcase err != nil:\n\t\t\tclog.Errorf(ctx, \"error reading from error stream for port %d: %v\", remotePort, err)\n\t\t\tpc.onClose()\n\t\tcase len(message) > 0:\n\t\t\tclog.Errorf(ctx, \"error forwarding to %d: %v\", remotePort, string(message))\n\t\t\tpc.onClose()\n\t\t}\n\t}()\n\n\t// create data stream\n\theaders.Set(core.StreamType, core.StreamTypeData)\n\tdataStream, err = pc.streamConn.CreateStream(headers)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error creating forwarding stream for port %d: %v\", remotePort, err)\n\t}\n\treturn &portConn{\n\t\tdialer:      pc,\n\t\tdataStream:  dataStream,\n\t\terrorStream: errorStream,\n\t}, nil\n}\n\nfunc (pc *podDialer) Close() error {\n\t// Must close before calling onClose, because the close\n\t// will release a channel that in some situations will\n\t// block the onClose().\n\terr := pc.streamConn.Close()\n\tif pc.onClose != nil {\n\t\tpc.onClose()\n\t}\n\treturn err\n}\n\n// portConn implements net.Conn and represents a connection to a specific port in a pod.\ntype portConn struct {\n\tdialer      *podDialer\n\tdataStream  httpstream.Stream\n\terrorStream httpstream.Stream\n}\n\nfunc (pc *portConn) Read(b []byte) (n int, err error) {\n\treturn pc.dataStream.Read(b)\n}\n\nfunc (pc *portConn) Write(b []byte) (int, error) {\n\tn, err := pc.dataStream.Write(b)\n\treturn n, err\n}\n\ntype addr string\n\nfunc (a addr) Network() string { return \"kubectl-port-forward\" }\nfunc (a addr) String() string  { return string(a) }\n\nfunc (pc *portConn) LocalAddr() net.Addr {\n\tif dataConn, ok := pc.dataStream.(net.Conn); ok {\n\t\treturn dataConn.LocalAddr()\n\t}\n\treturn addr(\"client\")\n}\n\nfunc (pc *portConn) RemoteAddr() net.Addr {\n\tif dataConn, ok := pc.dataStream.(net.Conn); ok {\n\t\treturn dataConn.RemoteAddr()\n\t}\n\treturn addr(\"server\")\n}\n\nfunc (pc *portConn) Close() error {\n\tpc.dataStream.Close()\n\tpc.errorStream.Close()\n\tpc.dialer.streamConn.RemoveStreams(pc.dataStream, pc.errorStream)\n\tif atomic.AddInt64(&pc.dialer.refCount, -1) == 0 {\n\t\treturn pc.dialer.Close()\n\t}\n\treturn nil\n}\n\nfunc (pc *portConn) SetDeadline(t time.Time) error {\n\tif dataConn, ok := pc.dataStream.(net.Conn); ok {\n\t\treturn dataConn.SetDeadline(t)\n\t}\n\treturn nil\n}\n\nfunc (pc *portConn) SetReadDeadline(t time.Time) error {\n\tif dataConn, ok := pc.dataStream.(net.Conn); ok {\n\t\treturn dataConn.SetReadDeadline(t)\n\t}\n\treturn nil\n}\n\nfunc (pc *portConn) SetWriteDeadline(t time.Time) error {\n\tif dataConn, ok := pc.dataStream.(net.Conn); ok {\n\t\treturn dataConn.SetWriteDeadline(t)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/remotefs/bridge.go",
    "content": "package remotefs\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/forwarder\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype bridgeMounter uint16\n\nfunc NewBridgeMounter(_ tunnel.SessionID, _ manager.ManagerClient, localPort uint16) Mounter {\n\treturn bridgeMounter(localPort)\n}\n\nfunc (m bridgeMounter) Start(ctx context.Context, _, _, _, _ string, podAddrPort netip.AddrPort, _ bool) error {\n\tctx = clog.WithGroup(ctx, podAddrPort.String())\n\tpp := types.PortAndProto{\n\t\tPort:  uint16(m),\n\t\tProto: types.ProtoTCP,\n\t}\n\tclog.Debugf(ctx, \"Remote mount bridge listening at :%d, will forward to %s\", m, podAddrPort)\n\tgo func() {\n\t\tf := forwarder.New(pp, tunnel.ClientToAgent, podAddrPort)\n\t\terr := f.Serve(ctx, nil)\n\t\tif err != nil && ctx.Err() == nil {\n\t\t\tclog.Errorf(ctx, \"port-forwarder failed with %v\", err)\n\t\t}\n\t}()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/remotefs/fuseftp.go",
    "content": "package remotefs\n\nimport (\n\t\"context\"\n\t_ \"embed\"\n\n\t\"github.com/telepresenceio/go-fuseftp/rpc\"\n)\n\ntype FuseFTPManager interface {\n\tLinkedFTP() bool\n\tDeferInit(ctx context.Context) error\n\tGetFuseFTPClient(ctx context.Context) rpc.FuseFTPClient\n}\n"
  },
  {
    "path": "pkg/client/remotefs/fuseftp_docker.go",
    "content": "//go:build docker\n\npackage remotefs\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/telepresenceio/go-fuseftp/rpc\"\n)\n\n// NewFTPMounter returns nil. It's here to satisfy the linker.\nfunc NewFTPMounter(rpc.FuseFTPClient, *sync.WaitGroup) Mounter {\n\treturn nil\n}\n\ntype fuseFtpMgr struct{}\n\nfunc NewFuseFTPManager() FuseFTPManager {\n\treturn &fuseFtpMgr{}\n}\n\nfunc (s *fuseFtpMgr) LinkedFTP() bool {\n\treturn false\n}\n\nfunc (s *fuseFtpMgr) DeferInit(context.Context) error {\n\treturn errors.New(\"fuseftp client is not available\")\n}\n\nfunc (s *fuseFtpMgr) GetFuseFTPClient(context.Context) rpc.FuseFTPClient {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/remotefs/fuseftp_embedded.go",
    "content": "//go:build !(docker || external_fuseftp || linked_fuseftp)\n\npackage remotefs\n\nimport (\n\t\"context\"\n\t_ \"embed\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\n//go:embed fuseftp.bits\nvar fuseftpBits []byte\n\nfunc getFuseFTPServer(ctx context.Context, exe string) (string, error) {\n\tqn := filepath.Join(filelocation.AppUserCacheDir(ctx), exe)\n\tvar sz int\n\tst, err := os.Stat(qn)\n\tif err != nil {\n\t\tif !errors.Is(err, fs.ErrNotExist) {\n\t\t\treturn \"\", err\n\t\t}\n\t\tsz = 0\n\t} else {\n\t\tsz = int(st.Size())\n\t}\n\tif len(fuseftpBits) != sz {\n\t\terr = os.WriteFile(qn, fuseftpBits, 0o700)\n\t}\n\treturn qn, err\n}\n"
  },
  {
    "path": "pkg/client/remotefs/fuseftp_external.go",
    "content": "//go:build external_fuseftp && !docker\n\npackage remotefs\n\nimport (\n\t\"context\"\n\t\"os/exec\"\n)\n\nfunc getFuseFTPServer(_ context.Context, exe string) (string, error) {\n\treturn exec.LookPath(exe)\n}\n"
  },
  {
    "path": "pkg/client/remotefs/fuseftp_grpc.go",
    "content": "//go:build !(linked_fuseftp || docker)\n\npackage remotefs\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"google.golang.org/protobuf/types/known/durationpb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/go-fuseftp/rpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\ntype ftpMounter struct {\n\tclient  rpc.FuseFTPClient\n\tid      *rpc.MountIdentifier\n\ticeptWG *sync.WaitGroup\n}\n\nfunc NewFTPMounter(client rpc.FuseFTPClient, iceptWG *sync.WaitGroup) Mounter {\n\treturn &ftpMounter{client: client, iceptWG: iceptWG}\n}\n\nfunc (m *ftpMounter) Start(ctx context.Context, workload, container, clientMountPoint, mountPoint string, podAddrPort netip.AddrPort, ro bool) error {\n\t// The FTPClient and the NewHost must be controlled by the intercept context and not by the pod context that\n\t// is passed as a parameter, because those services will survive pod changes.\n\troTxt := \"\"\n\tif ro {\n\t\troTxt = \" read-only\"\n\t}\n\tif m.id == nil {\n\t\tcfg := client.GetConfig(ctx)\n\t\tclog.Infof(ctx, \"Mounting FTP file system for container %s[%s] (address %s)%s at %q\", workload, container, podAddrPort, roTxt, clientMountPoint)\n\t\t// FTPs remote mount is already relative to the agentconfig.ExportsMountPoint\n\t\trmp := strings.TrimPrefix(mountPoint, agentconfig.ExportsMountPoint)\n\t\tcc, cancel := context.WithTimeout(ctx, 3*time.Second)\n\t\tmountId, err := m.client.Mount(cc, &rpc.MountRequest{\n\t\t\tMountPoint: clientMountPoint,\n\t\t\tFtpServer: &rpc.AddressAndPort{\n\t\t\t\tIp:   podAddrPort.Addr().AsSlice(),\n\t\t\t\tPort: int32(podAddrPort.Port()),\n\t\t\t},\n\t\t\tReadTimeout: durationpb.New(cfg.Timeouts().Get(client.TimeoutFtpReadWrite)),\n\t\t\tDirectory:   rmp,\n\t\t\tReadOnly:    ro,\n\t\t})\n\t\tcancel()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tm.id = mountId\n\n\t\t// Ensure unmount when intercept context is cancelled\n\t\tm.iceptWG.Add(1)\n\t\tgo func() {\n\t\t\tdefer m.iceptWG.Done()\n\t\t\t<-ctx.Done()\n\t\t\tctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), cfg.Timeouts().Get(client.TimeoutFtpShutdown))\n\t\t\tdefer cancel()\n\t\t\tclog.Debugf(ctx, \"Unmounting FTP file system for container %s[%s] (address %s) at %q\", workload, container, podAddrPort, clientMountPoint)\n\t\t\tif _, err = m.client.Unmount(ctx, m.id); err != nil {\n\t\t\t\tclog.Errorf(ctx, \"Unmount of %s failed: %v\", clientMountPoint, err)\n\t\t\t} else {\n\t\t\t\tclog.Debugf(ctx, \"FTP file system for container %s[%s] (address %s) successfully unmounted\", workload, container, podAddrPort)\n\t\t\t}\n\t\t}()\n\t\tclog.Infof(ctx, \"File system for container %s[%s] (address %s) successfully mounted%s at %q\", workload, container, podAddrPort, roTxt, clientMountPoint)\n\t\treturn nil\n\t}\n\n\t// Assign a new address to the FTP client. This kills any open connections but leaves the FUSE driver intact\n\tclog.Infof(ctx, \"Switching remote address to %s for FTP file system for workload container %s[%s] at %q\", podAddrPort, workload, container, clientMountPoint)\n\t_, err := m.client.SetFtpServer(ctx, &rpc.SetFtpServerRequest{\n\t\tFtpServer: &rpc.AddressAndPort{\n\t\t\tIp:   podAddrPort.Addr().AsSlice(),\n\t\t\tPort: int32(podAddrPort.Port()),\n\t\t},\n\t\tId: m.id,\n\t})\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/remotefs/fuseftp_linked.go",
    "content": "//go:build linked_fuseftp && !docker\n\npackage remotefs\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/go-fuseftp/pkg/fs\"\n\t\"github.com/telepresenceio/go-fuseftp/rpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\ntype ftpMounter struct {\n\tmountPoint string\n\tcancel     context.CancelFunc\n\tftpClient  fs.FTPClient\n\ticeptWG    *sync.WaitGroup\n}\n\ntype fuseFtpMgr struct{}\n\nfunc (s *fuseFtpMgr) LinkedFTP() bool {\n\treturn true\n}\n\nfunc NewFuseFTPManager() FuseFTPManager {\n\treturn &fuseFtpMgr{}\n}\n\nfunc (s *fuseFtpMgr) DeferInit(_ context.Context) error {\n\treturn nil\n}\n\nfunc (s *fuseFtpMgr) GetFuseFTPClient(_ context.Context) rpc.FuseFTPClient {\n\treturn rpc.NewFuseFTPClient(nil)\n}\n\nfunc NewFTPMounter(_ rpc.FuseFTPClient, iceptWG *sync.WaitGroup) Mounter {\n\treturn &ftpMounter{iceptWG: iceptWG}\n}\n\nfunc (m *ftpMounter) Start(ctx context.Context, workload, container, clientMountPoint, mountPoint string, podAddrPort netip.AddrPort, ro bool) error {\n\troTxt := \"\"\n\tif ro {\n\t\troTxt = \" read-only\"\n\t}\n\tif m.ftpClient == nil {\n\t\tcfg := client.GetConfig(ctx)\n\t\tclog.Infof(ctx, \"Mounting FTP file system for container %s[%s] (address %s)%s at %q\", workload, container, podAddrPort, roTxt, clientMountPoint)\n\t\t// FTPs remote mount is already relative to the agentconfig.ExportsMountPoint\n\t\trmp := strings.TrimPrefix(mountPoint, agentconfig.ExportsMountPoint)\n\t\tftpClient, err := fs.NewFTPClient(ctx.Done(), podAddrPort, rmp, ro, cfg.Timeouts().Get(client.TimeoutFtpReadWrite))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\thost := fs.NewHost(ftpClient, clientMountPoint)\n\t\tif err = host.Start(ctx, 5*time.Second); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tm.ftpClient = ftpClient\n\t\t// Ensure unmount when intercept context is cancelled\n\t\tm.iceptWG.Add(1)\n\t\tgo func() {\n\t\t\tdefer m.iceptWG.Done()\n\t\t\t<-ctx.Done()\n\t\t\tclog.Debugf(ctx, \"Unmounting FTP file system for container %s[%s] (address %s) at %q\", workload, container, podAddrPort, clientMountPoint)\n\t\t}()\n\t\tclog.Infof(ctx, \"File system for container %s[%s] (address %s) successfully mounted%s at %q\", workload, container, podAddrPort, roTxt, clientMountPoint)\n\t\treturn nil\n\t}\n\n\t// Assign a new address to the FTP client. This kills any open connections but leaves the FUSE driver intact\n\tclog.Infof(ctx, \"Switching remote address to %s for FTP file system for workload container %s[%s] at %q\", podAddrPort, workload, container, clientMountPoint)\n\treturn m.ftpClient.SetAddress(podAddrPort)\n}\n"
  },
  {
    "path": "pkg/client/remotefs/fuseftp_other.go",
    "content": "//go:build !docker && !linked_fuseftp\n\npackage remotefs\n\nimport (\n\t\"context\"\n\t_ \"embed\"\n\t\"log/slog\"\n\t\"os\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/go-fuseftp/rpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\ntype fuseFtpMgr struct {\n\tstartFuseCh chan struct{}\n\tfuseFtpCh   chan rpc.FuseFTPClient\n}\n\nfunc NewFuseFTPManager() FuseFTPManager {\n\treturn &fuseFtpMgr{\n\t\tstartFuseCh: make(chan struct{}),\n\t\tfuseFtpCh:   make(chan rpc.FuseFTPClient, 1),\n\t}\n}\n\nfunc (s *fuseFtpMgr) LinkedFTP() bool {\n\treturn false\n}\n\nfunc (s *fuseFtpMgr) DeferInit(ctx context.Context) error {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil\n\tcase <-s.startFuseCh:\n\t}\n\treturn runFuseFTPServer(ctx, s.fuseFtpCh)\n}\n\nfunc (s *fuseFtpMgr) GetFuseFTPClient(ctx context.Context) rpc.FuseFTPClient {\n\t// Close startFuseFtp unless it's already closed. This will kick\n\t// the DeferInit to either make the client available on\n\t// the fuseFtpCh or close that channel\n\tselect {\n\tcase <-s.startFuseCh:\n\tdefault:\n\t\tclose(s.startFuseCh)\n\t}\n\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil\n\tcase c, ok := <-s.fuseFtpCh:\n\t\tif ok {\n\t\t\t// Put the client back onto the queue for the next caller to read\n\t\t\ts.fuseFtpCh <- c\n\t\t}\n\t\treturn c\n\t}\n}\n\n// runFuseFtpServer ensures that the fuseftp gRPC server is downloaded into the\n// user cache, and starts it. Once the socket is created by the server, a\n// client is connected and written to the given channel.\n//\n// The server dies when the given context is cancelled.\nfunc runFuseFTPServer(ctx context.Context, cCh chan<- rpc.FuseFTPClient) error {\n\tcloseCh := true\n\tdefer func() {\n\t\tif closeCh {\n\t\t\tclose(cCh)\n\t\t}\n\t}()\n\n\texe := \"fuseftp\"\n\tif runtime.GOOS == \"windows\" {\n\t\texe = \"fuseftp.exe\"\n\t}\n\tqn, err := getFuseFTPServer(ctx, exe)\n\tif err != nil {\n\t\tclog.Warnf(ctx, \"no fuseftp server is installed in PATH %s, FTP mounts will be disabled: %v\", os.Getenv(\"PATH\"), err)\n\t\treturn err\n\t}\n\tclog.Infof(ctx, \"using FuseFTP server %s\", qn)\n\n\tsf, err := os.CreateTemp(\"\", \"fuseftp-*.socket\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tsocketName := sf.Name()\n\t_ = sf.Close()\n\t_ = os.Remove(socketName)\n\n\tcmd := proc.CommandContext(ctx, qn, socketName)\n\n\tcmd.Stderr = clog.StdLogger(ctx, slog.LevelError).Writer()\n\tcmd.Stdout = clog.StdLogger(ctx, slog.LevelInfo).Writer()\n\terr = cmd.Start()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcloseCh = false // closing the channel is now the responsibility of waitForSocketAndConnect\n\twaitForSocketAndConnect(ctx, socketName, cCh)\n\treturn cmd.Wait()\n}\n\nfunc waitForSocketAndConnect(ctx context.Context, socketName string, cCh chan<- rpc.FuseFTPClient) {\n\tconn, err := dial(ctx, socketName)\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\tclose(cCh)\n\t\treturn\n\t}\n\tselect {\n\tcase <-ctx.Done():\n\tcase cCh <- rpc.NewFuseFTPClient(conn):\n\t}\n}\n\nfunc dial(ctx context.Context, socketName string) (conn *grpc.ClientConn, err error) {\n\terr = backoff.Retry(func() (err error) {\n\t\tconn, err = grpc.NewClient(\"unix:\"+socketName,\n\t\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t\t\tgrpc.WithNoProxy())\n\t\treturn err\n\t}, backoff.WithContext(backoff.WithMaxRetries(backoff.NewConstantBackOff(200*time.Millisecond), 5), ctx))\n\treturn conn, err\n}\n"
  },
  {
    "path": "pkg/client/remotefs/mounter.go",
    "content": "package remotefs\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n)\n\n// A Mounter is responsible for mounting a remote filesystem in a local directory or drive letter.\ntype Mounter interface {\n\t// Start mounts the remote directory given by mountPoint on the local directory or drive letter\n\t// given ty clientMountPoint. The podIP and port is the address to the remote FTP or SFTP server.\n\t// The id is just used for logging purposes.\n\tStart(ctx context.Context, workload, container, clientMountPoint, mountPoint string, podAddrPort netip.AddrPort, ro bool) error\n}\n"
  },
  {
    "path": "pkg/client/remotefs/sftp.go",
    "content": "package remotefs\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dpipe\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\ntype sftpMounter struct {\n\tsync.Mutex\n\ticeptWG *sync.WaitGroup\n\tpodWG   *sync.WaitGroup\n}\n\nfunc NewSFTPMounter(iceptWG, podWG *sync.WaitGroup) Mounter {\n\treturn &sftpMounter{iceptWG: iceptWG, podWG: podWG}\n}\n\nfunc (m *sftpMounter) Start(ctx context.Context, workload, container, clientMountPoint, mountPoint string, podAddrPort netip.AddrPort, ro bool) error {\n\tctx = clog.WithGroup(ctx, podAddrPort.String())\n\tpodIP := podAddrPort.Addr().Unmap()\n\n\t// The mount is terminated and restarted when the intercept pod changes, so we\n\t// must set up a wait/done pair here to ensure that this happens synchronously\n\tm.podWG.Add(1)\n\tm.iceptWG.Add(1)\n\tgo func() {\n\t\tdefer m.iceptWG.Done()\n\t\tdefer m.podWG.Done()\n\n\t\t// Be really sure that the following doesn't happen in parallel using multiple\n\t\t// pods for the same intercept. One must die before the next is created.\n\t\tm.Lock()\n\t\tdefer m.Unlock()\n\n\t\tclog.Infof(ctx, \"Mounting SFTP file system for container %s[%s] (pod %s) at %q\", workload, container, podIP, clientMountPoint)\n\t\tif runtime.GOOS != \"windows\" {\n\t\t\tdefer func() {\n\t\t\t\tclog.Infof(ctx, \"Unmounting SFTP file system for container %s[%s] (pod %s) at %q\", workload, container, podIP, clientMountPoint)\n\t\t\t\ttime.Sleep(time.Second)\n\n\t\t\t\t// sshfs sometimes leave the mount point in a bad state. This will clean it up\n\t\t\t\tctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second)\n\t\t\t\tdefer cancel()\n\t\t\t\tvar umount *exec.Cmd\n\t\t\t\tif runtime.GOOS == \"darwin\" {\n\t\t\t\t\tumount = proc.CommandContext(ctx, \"umount\", \"-f\", clientMountPoint)\n\t\t\t\t} else {\n\t\t\t\t\tumount = proc.CommandContext(ctx, \"fusermount\", \"-uz\", clientMountPoint)\n\t\t\t\t}\n\t\t\t\t_ = umount.Run()\n\t\t\t}()\n\t\t}\n\n\t\t// Retry mount in case it gets disconnected\n\t\tbc := backoff.WithContext(backoff.NewConstantBackOff(3*time.Second), ctx)\n\t\terr := backoff.Retry(func() error {\n\t\t\tsshfsArgs := []string{\n\t\t\t\t\"-F\", \"none\", // don't load the user's config file\n\t\t\t\t\"-f\", // foreground operation\n\n\t\t\t\t// connection settings\n\t\t\t\t\"-C\", // compression\n\t\t\t\t\"-oConnectTimeout=10\",\n\n\t\t\t\t// mount directives\n\t\t\t\t\"-o\", \"follow_symlinks\",\n\t\t\t\t\"-o\", \"allow_root\", // needed to make --docker-run work as docker runs as root\n\t\t\t}\n\t\t\tif ro {\n\t\t\t\tsshfsArgs = append(sshfsArgs, \"-o\", \"ro\")\n\t\t\t}\n\n\t\t\tuseIPv6 := podIP.Is6()\n\t\t\tif useIPv6 {\n\t\t\t\t// Must use stdin/stdout because sshfs is not capable of connecting with IPv6\n\t\t\t\tsshfsArgs = append(sshfsArgs,\n\t\t\t\t\t\"-o\", \"slave\",\n\t\t\t\t\tfmt.Sprintf(\"localhost:%s\", mountPoint),\n\t\t\t\t\tclientMountPoint, // where to mount it\n\t\t\t\t)\n\t\t\t} else {\n\t\t\t\tsshfsArgs = append(sshfsArgs,\n\t\t\t\t\t\"-o\", fmt.Sprintf(\"directport=%d\", podAddrPort.Port()),\n\t\t\t\t\tfmt.Sprintf(\"%s:%s\", podIP.String(), mountPoint), // what to mount\n\t\t\t\t\tclientMountPoint, // where to mount it\n\t\t\t\t)\n\t\t\t}\n\n\t\t\texe := \"sshfs\"\n\t\t\tif runtime.GOOS == \"windows\" {\n\t\t\t\t// Use sshfs-win to launch the sshfs\n\t\t\t\tsshfsArgs = append([]string{\"cmd\", \"-ouid=-1\", \"-ogid=-1\"}, sshfsArgs...)\n\t\t\t\texe = \"sshfs-win\"\n\t\t\t}\n\t\t\tvar err error\n\t\t\tif useIPv6 {\n\t\t\t\tvar conn net.Conn\n\t\t\t\tif conn, err = net.Dial(\"tcp6\", podAddrPort.String()); err == nil {\n\t\t\t\t\tdefer conn.Close()\n\t\t\t\t\terr = dpipe.DPipe(ctx, conn, exe, sshfsArgs...)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terr = proc.Run(ctx, nil, exe, sshfsArgs...)\n\t\t\t}\n\t\t\treturn err\n\t\t}, bc)\n\t\tif err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t}\n\t}()\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/rootd/dbus/resolved.go",
    "content": "//go:build linux\n\npackage dbus\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"strings\"\n\n\t\"github.com/godbus/dbus/v5\"\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\ntype (\n\t// A resolvedLinkAddress is the type of the array members of the argument to the SetLinkDNS DBus call. It consists\n\t// of an address family (either AF_INET or AF_INET6), followed by a 4-byte or 16-byte array with the raw address data.\n\tresolvedLinkAddress struct {\n\t\tDialect int32\n\t\tIP      net.IP\n\t}\n\n\t// A resolvedDomain is the type of the array members of the argument to the SetLinkDomains DBus call. It is a domain\n\t// name string and a parameter identifying whether to include that domain in the search path, or only to be used for\n\t// deciding which DNS server to route a given request to.\n\tresolvedDomain struct {\n\t\tName        string\n\t\tRoutingOnly bool\n\t}\n)\n\nfunc withDBus(c context.Context, f func(*dbus.Conn) error) error {\n\tconn, err := dbus.ConnectSystemBus()\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to connect to system bus: %w\", err)\n\t\tclog.Error(c, err)\n\t\treturn err\n\t}\n\tdefer conn.Close()\n\treturn f(conn)\n}\n\nfunc IsResolveDRunning(c context.Context) bool {\n\terr := withDBus(c, func(conn *dbus.Conn) error {\n\t\tvar names []string\n\t\tif err := conn.BusObject().CallWithContext(c, \"org.freedesktop.DBus.ListNames\", 0).Store(&names); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, name := range names {\n\t\t\tif name == \"org.freedesktop.resolve1\" {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn errors.New(\"not found\")\n\t})\n\treturn err == nil\n}\n\nfunc SetLinkDNS(c context.Context, networkIndex int, ips ...net.IP) error {\n\treturn withDBus(c, func(conn *dbus.Conn) error {\n\t\taddrs := make([]resolvedLinkAddress, len(ips))\n\t\tfor i, ip := range ips {\n\t\t\taddr := &addrs[i]\n\t\t\tswitch len(ip) {\n\t\t\tcase 4:\n\t\t\t\taddr.Dialect = unix.AF_INET\n\t\t\tcase 16:\n\t\t\t\taddr.Dialect = unix.AF_INET6\n\t\t\tdefault:\n\t\t\t\treturn errors.New(\"illegal IP (not AF_INET or AF_INET6\")\n\t\t\t}\n\t\t\taddr.IP = ip\n\t\t}\n\t\treturn conn.Object(\"org.freedesktop.resolve1\", \"/org/freedesktop/resolve1\").CallWithContext(\n\t\t\tc, \"org.freedesktop.resolve1.Manager.SetLinkDNS\", 0, int32(networkIndex), addrs).Err\n\t})\n}\n\nfunc SetLinkDomains(c context.Context, networkIndex int, domains ...string) error {\n\treturn withDBus(c, func(conn *dbus.Conn) error {\n\t\tdds := make([]resolvedDomain, 0, len(domains))\n\t\tfor _, domain := range domains {\n\t\t\tif domain == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trouting := false\n\t\t\tif strings.HasPrefix(domain, \"~\") {\n\t\t\t\tdomain = domain[1:]\n\t\t\t\trouting = true\n\t\t\t}\n\t\t\tdds = append(dds, resolvedDomain{Name: domain, RoutingOnly: routing})\n\t\t}\n\t\treturn conn.Object(\"org.freedesktop.resolve1\", \"/org/freedesktop/resolve1\").CallWithContext(\n\t\t\tc, \"org.freedesktop.resolve1.Manager.SetLinkDomains\", 0, int32(networkIndex), dds).Err\n\t})\n}\n\nfunc RevertLink(c context.Context, networkIndex int) error {\n\treturn withDBus(c, func(conn *dbus.Conn) error {\n\t\treturn conn.Object(\"org.freedesktop.resolve1\", \"/org/freedesktop/resolve1\").CallWithContext(\n\t\t\tc, \"org.freedesktop.resolve1.Manager.RevertLink\", 0, int32(networkIndex)).Err\n\t})\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/client.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\n\t\"github.com/miekg/dns\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\ntype luResult struct {\n\tnetip.Addr\n\trCode int\n}\n\n// LookupIP performs an A and an AAAA query and returns the first answer.\nfunc LookupIP(ctx context.Context, localDNS netip.AddrPort, name string) (ip netip.Addr, err error) {\n\tc := new(dns.Client)\n\tctx, cancel := context.WithTimeout(ctx, client.GetConfig(ctx).DNS().LookupTimeout)\n\tdefer cancel()\n\n\tdnsAddr := localDNS.String()\n\tqName := dns.Fqdn(name)\n\tch := make(chan luResult, 2)\n\n\tgo lookupIP(ctx, c, dnsAddr, qName, dns.TypeA, ch)\n\tgo lookupIP(ctx, c, dnsAddr, qName, dns.TypeAAAA, ch)\n\n\tfor i := 0; i < 2; i++ {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn ip, status.Error(codes.Canceled, ctx.Err().Error())\n\t\tcase r := <-ch:\n\t\t\tswitch r.rCode {\n\t\t\tcase dns.RcodeSuccess:\n\t\t\t\tif r.IsValid() {\n\t\t\t\t\treturn r.Addr, nil\n\t\t\t\t}\n\t\t\tcase dns.RcodeNameError:\n\t\t\tdefault:\n\t\t\t\treturn ip, status.Error(codes.Internal, fmt.Sprintf(\"unable to resolve name %s: %s\", name, dns.RcodeToString[r.rCode]))\n\t\t\t}\n\t\t}\n\t}\n\treturn ip, status.Error(codes.NotFound, fmt.Sprintf(\"unable to resolve name %s\", name))\n}\n\nfunc lookupIP(ctx context.Context, c *dns.Client, localDNS, name string, qType uint16, ch chan<- luResult) {\n\tm := new(dns.Msg)\n\tm.SetQuestion(name, qType)\n\tr, _, err := c.ExchangeContext(ctx, m, localDNS)\n\tif err != nil {\n\t\trCode := dns.RcodeServerFailure\n\t\tvar opErr *net.OpError\n\t\tswitch {\n\t\tcase errors.Is(err, context.DeadlineExceeded), errors.Is(err, context.Canceled):\n\t\t\trCode = dns.RcodeNameError\n\t\tcase errors.As(err, &opErr) && opErr.Timeout():\n\t\t\trCode = dns.RcodeNameError\n\t\tdefault:\n\t\t\tclog.Errorf(ctx, \"dns.ExchangeContext: %v\", err)\n\t\t}\n\t\tch <- luResult{rCode: rCode}\n\t\treturn\n\t}\n\tif r.Rcode != dns.RcodeSuccess {\n\t\tch <- luResult{rCode: r.Rcode}\n\t\treturn\n\t}\n\tfor _, rr := range r.Answer {\n\t\tif rr.Header().Rrtype == qType {\n\t\t\tvar bs net.IP\n\t\t\tswitch qType {\n\t\t\tcase dns.TypeA:\n\t\t\t\tbs = rr.(*dns.A).A\n\t\t\tcase dns.TypeAAAA:\n\t\t\t\tbs = rr.(*dns.AAAA).AAAA\n\t\t\t}\n\t\t\tif len(bs) > 0 {\n\t\t\t\tif ip, ok := netip.AddrFromSlice(bs); ok {\n\t\t\t\t\tch <- luResult{Addr: ip, rCode: dns.RcodeSuccess}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tch <- luResult{rCode: dns.RcodeSuccess}\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/client_queue.go",
    "content": "package dns\n\nimport (\n\t\"time\"\n\n\t\"github.com/miekg/dns\"\n)\n\n// Most of this was aped off the docs at https://pkg.go.dev/container/heap@go1.17.8\n\ntype waitingClient struct {\n\treturnCh    chan *dns.Conn\n\tdoneCh      <-chan struct{}\n\tarrivalTime time.Time\n\tindex       int // The index of the item in the heap.\n}\n\n// A clientQueue implements heap.Interface and holds waitingClients.\ntype clientQueue []*waitingClient\n\nfunc (pq clientQueue) Len() int { return len(pq) }\n\nfunc (pq clientQueue) Less(i, j int) bool {\n\treturn pq[i].arrivalTime.Before(pq[j].arrivalTime)\n}\n\nfunc (pq clientQueue) Swap(i, j int) {\n\tpq[i], pq[j] = pq[j], pq[i]\n\tpq[i].index = i\n\tpq[j].index = j\n}\n\nfunc (pq *clientQueue) Push(x any) {\n\tn := len(*pq)\n\titem := x.(*waitingClient)\n\titem.index = n\n\t*pq = append(*pq, item)\n}\n\nfunc (pq *clientQueue) Pop() any {\n\told := *pq\n\tn := len(old)\n\titem := old[n-1]\n\told[n-1] = nil  // avoid memory leak\n\titem.index = -1 // for safety\n\t*pq = old[0 : n-1]\n\treturn item\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/connpool.go",
    "content": "package dns\n\nimport (\n\t\"container/heap\"\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/miekg/dns\"\n)\n\ntype ConnPool struct {\n\titems      map[*dns.Conn]bool\n\tnewArrival chan *waitingClient\n\tfinished   chan *dns.Conn\n\tclients    clientQueue\n\tcancel     context.CancelFunc\n\tremoteAddr netip.AddrPort\n}\n\nfunc NewConnPool(addr netip.AddrPort, poolSize int) (*ConnPool, error) {\n\tcCtx, cCancel := context.WithCancel(context.Background())\n\tpool := &ConnPool{\n\t\titems:      make(map[*dns.Conn]bool, poolSize),\n\t\tnewArrival: make(chan *waitingClient),\n\t\tfinished:   make(chan *dns.Conn),\n\t\tcancel:     cCancel,\n\t\tremoteAddr: addr,\n\t}\n\theap.Init(&pool.clients)\n\tfor i := 0; i < poolSize; i++ {\n\t\tconn, err := dns.Dial(\"udp\", addr.String())\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to create DNS conn to %s: %w\", addr, err)\n\t\t}\n\t\tpool.items[conn] = false\n\t}\n\tgo pool.coordinate(cCtx)\n\treturn pool, nil\n}\n\nfunc (cp *ConnPool) LocalAddrs() []netip.AddrPort {\n\tretval := make([]netip.AddrPort, len(cp.items))\n\ti := 0\n\tfor conn := range cp.items {\n\t\tretval[i] = conn.LocalAddr().(*net.UDPAddr).AddrPort()\n\t\ti++\n\t}\n\treturn retval\n}\n\nfunc (cp *ConnPool) RemoteAddr() netip.AddrPort {\n\treturn cp.remoteAddr\n}\n\nfunc (cp *ConnPool) Exchange(ctx context.Context, client *dns.Client, msg *dns.Msg) (r *dns.Msg, rtt time.Duration, err error) {\n\tconn, err := cp.getConnection(ctx)\n\tif err != nil {\n\t\treturn nil, time.Duration(0), err\n\t}\n\tdefer cp.releaseConnection(conn)\n\treturn client.ExchangeWithConn(msg, conn)\n}\n\nfunc (cp *ConnPool) Close() {\n\tcp.cancel()\n}\n\nfunc (cp *ConnPool) coordinate(ctx context.Context) {\n\tdefer func() {\n\t\tfor conn := range cp.items {\n\t\t\tconn.Close()\n\t\t}\n\t}()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase client := <-cp.newArrival:\n\t\t\theap.Push(&cp.clients, client)\n\t\tcase conn := <-cp.finished:\n\t\t\tcp.items[conn] = false\n\t\t}\n\t\tfor conn, inUse := range cp.items {\n\t\t\tif !inUse && len(cp.clients) > 0 {\n\t\t\t\tcp.items[conn] = true\n\t\t\t\tclient := heap.Pop(&cp.clients).(*waitingClient)\n\t\t\t\tselect {\n\t\t\t\tcase client.returnCh <- conn:\n\t\t\t\tcase <-client.doneCh:\n\t\t\t\t\tcp.items[conn] = false\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (cp *ConnPool) getConnection(ctx context.Context) (*dns.Conn, error) {\n\tclient := &waitingClient{\n\t\tarrivalTime: time.Now(),\n\t\treturnCh:    make(chan *dns.Conn),\n\t\tdoneCh:      ctx.Done(),\n\t}\n\tselect {\n\tcase cp.newArrival <- client:\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\t}\n\tselect {\n\tcase conn := <-client.returnCh:\n\t\treturn conn, nil\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\t}\n}\n\nfunc (cp *ConnPool) releaseConnection(conn *dns.Conn) {\n\tcp.finished <- conn\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/connpool_test.go",
    "content": "//go:build linux\n\npackage dns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/miekg/dns\"\n)\n\nfunc TestConnPoolConcurrency(t *testing.T) {\n\tconst (\n\t\tTOTAL_THREADS       = 15\n\t\tREQUESTS_PER_THREAD = 5\n\t\tTIMEOUT_S           = 8\n\t)\n\tctx := context.Background()\n\tdc := &dns.Client{\n\t\tNet:     \"udp\",\n\t\tTimeout: TIMEOUT_S * time.Second,\n\t}\n\tpool, err := NewConnPool(netip.MustParseAddrPort(\"8.8.8.8:53\"), 5)\n\tif err != nil {\n\t\tt.Log(err)\n\t\tt.FailNow()\n\t}\n\tdefer pool.Close()\n\terrors := make(chan error, TOTAL_THREADS*REQUESTS_PER_THREAD)\n\twg := &sync.WaitGroup{}\n\twg.Add(TOTAL_THREADS)\n\tfor i := 0; i < TOTAL_THREADS; i++ {\n\t\tgo func(idx int) {\n\t\t\tdefer wg.Done()\n\t\t\tfor j := 0; j < REQUESTS_PER_THREAD; j++ {\n\t\t\t\tmsg := new(dns.Msg)\n\t\t\t\tdomain := fmt.Sprintf(\"dns-test-%d.preview.edgestack.me.\", idx)\n\t\t\t\tmsg.SetQuestion(domain, dns.TypeMX)\n\t\t\t\tctx, cancel := context.WithTimeout(ctx, TIMEOUT_S*time.Second)\n\t\t\t\t_, _, err := pool.Exchange(ctx, dc, msg)\n\t\t\t\tcancel()\n\t\t\t\tif err != nil {\n\t\t\t\t\terrors <- err\n\t\t\t\t}\n\t\t\t}\n\t\t}(i)\n\t}\n\twg.Wait()\n\tclose(errors)\n\tfor err := range errors {\n\t\tif err != nil {\n\t\t\tt.Error(err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/resolved_linux.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/rootd/dbus\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\nfunc (s *Server) tryResolveD(c context.Context, dev vif.Device, configureDNS func(netip.AddrPort, netip.AddrPort)) error {\n\t// Connect to ResolveD via DBUS.\n\tif !dbus.IsResolveDRunning(c) {\n\t\tclog.Error(c, \"systemd-resolved is not running\")\n\t\treturn errResolveDNotConfigured\n\t}\n\n\tc, cancelResolveD := context.WithCancel(c)\n\tdefer cancelResolveD()\n\n\tlisteners, err := s.dnsListeners(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\t// Create a new local address that the DNS resolver can listen to.\n\tdnsResolverAddr, err := splitToUDPAddr(listeners[0].LocalAddr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tconfigureDNS(s.VIFAddress, dnsResolverAddr)\n\n\tg := log.NewGroup(c)\n\n\t// DNS resolver\n\tinitDone := make(chan struct{})\n\n\tg.Go(\"Server\", func(c context.Context) error {\n\t\tclog.Infof(c, \"Configuring DNS IP %s\", s.VIFAddress)\n\t\tif s.VIFAddress.Port() != 53 {\n\t\t\treturn fmt.Errorf(\"DBUS link only accepts DNS address with port 53, got %s\", s.VIFAddress)\n\t\t}\n\t\tif err = dbus.SetLinkDNS(c, int(dev.Index()), s.VIFAddress.Addr().AsSlice()); err != nil {\n\t\t\tclog.Error(c, err)\n\t\t\tinitDone <- struct{}{}\n\t\t\treturn errResolveDNotConfigured\n\t\t}\n\t\tdefer func() {\n\t\t\t// It's very likely that the context is cancelled here. We use it\n\t\t\t// anyway, stripped from cancellation, to retain logging.\n\t\t\tc, cancel := context.WithTimeout(context.WithoutCancel(c), time.Second)\n\t\t\tdefer cancel()\n\t\t\tclog.Debugf(c, \"Reverting Link settings for %s\", dev.Name())\n\t\t\tconfigureDNS(netip.AddrPort{}, netip.AddrPort{}) // Don't route from TUN-device\n\t\t\tif err = dbus.RevertLink(c, int(dev.Index())); err != nil {\n\t\t\t\tclog.Error(c, err)\n\t\t\t}\n\t\t\t// No need to close listeners here. They are closed by the dnsServer\n\t\t}()\n\t\tif err = s.updateLinkDomains(c, dev); err != nil {\n\t\t\tclog.Error(c, err)\n\t\t\tinitDone <- struct{}{}\n\t\t\treturn errResolveDNotConfigured\n\t\t}\n\t\treturn s.Run(c, initDone, listeners, nil)\n\t})\n\n\tg.Go(\"SanityCheck\", func(c context.Context) error {\n\t\tif _, ok := <-initDone; ok {\n\t\t\t// initDone was not closed, bail out.\n\t\t\treturn errResolveDNotConfigured\n\t\t}\n\n\t\t// Check if an attempt to resolve a DNS address reaches our DNS resolver, Two seconds should be plenty\n\t\tcmdC, cmdCancel := context.WithTimeout(c, 2*time.Second)\n\t\tdefer cmdCancel()\n\t\tfor cmdC.Err() == nil {\n\t\t\tgo func() {\n\t\t\t\tclog.Debug(cmdC, \"sanity-check lookup\")\n\t\t\t\t_, _ = net.DefaultResolver.LookupHost(cmdC, santiyCheck)\n\t\t\t\tif s.RequestCount() > 0 {\n\t\t\t\t\tcmdCancel()\n\t\t\t\t}\n\t\t\t}()\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t}\n\t\t<-cmdC.Done()\n\t\tif s.RequestCount() > 0 {\n\t\t\t// The query went all way through. Start processing search paths systemd-resolved style\n\t\t\t// and return nil for successful validation.\n\t\t\ts.processSearchPaths(g, s.updateLinkDomains, dev)\n\t\t\treturn nil\n\t\t}\n\t\ts.flushDNS()\n\t\tclog.Error(c, \"resolver did not receive requests from systemd-resolved\")\n\t\treturn errResolveDNotConfigured\n\t})\n\treturn g.Wait()\n}\n\nfunc (s *Server) updateLinkDomains(c context.Context, dev vif.Device) error {\n\ts.Lock()\n\tpaths := make([]string, len(s.search)+len(s.routes)+len(s.IncludeSuffixes)+1)\n\n\t// Namespaces are copied verbatim. Entries that aren't prefixed with \"~\" are considered search path entries.\n\tcopy(paths, s.search)\n\ti := len(s.search)\n\tfor ns := range s.routes {\n\t\tpaths[i] = \"~\" + ns\n\t\ti++\n\t}\n\n\t// Include-suffixes are routes, i.e. in contrast to search paths, they are never appended to the name, but\n\t// used as a filter that will direct queries for names ending with them to this resolver. Routes must be\n\t// prefixed with \"~\".\n\tfor _, sfx := range s.IncludeSuffixes {\n\t\tif !strings.HasSuffix(sfx, \".\") {\n\t\t\tsfx += \".\"\n\t\t}\n\t\tpaths[i] = \"~\" + strings.TrimPrefix(sfx, \".\")\n\t\ti++\n\t}\n\tpaths[i] = \"~\" + s.clusterDomain\n\ts.Unlock()\n\n\tif err := dbus.SetLinkDomains(c, int(dev.Index()), paths...); err != nil {\n\t\treturn fmt.Errorf(\"failed to set link domains on %q: %w\", dev.Name(), err)\n\t}\n\ts.flushDNS()\n\tclog.Debugf(c, \"Link domains on device %q set to [%s]\", dev.Name(), strings.Join(paths, \",\"))\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/server.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dnsproxy\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/slice\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\ntype Resolver func(context.Context, *dns.Question) (dnsproxy.RRs, int, error)\n\nconst (\n\t// defaultClusterDomain used unless traffic-manager reports otherwise.\n\tdefaultClusterDomain = \"cluster.local.\"\n\n\t// sanityCheck is the query used when verifying that a DNS query reaches our DNS server. It should result\n\t// in an increase of the requestCount but always yield an NXDOMAIN reply.\n\tsantiyCheck    = \"jhfweoitnkgyeta.\" + client.Tel2SubDomain\n\tsantiyCheckDot = santiyCheck + \".\"\n\n\t// dnsTTL is the number of seconds that a found DNS record should be allowed to live in the callers cache. We\n\t// keep this low to avoid such caching.\n\tdnsTTL = 4\n)\n\ntype FallbackPool interface {\n\tExchange(context.Context, *dns.Client, *dns.Msg) (*dns.Msg, time.Duration, error)\n\tRemoteAddr() netip.AddrPort\n\tLocalAddrs() []netip.AddrPort\n\tClose()\n}\n\nconst (\n\t_ = int32(iota)\n\trecursionQueryNotYetReceived\n\trecursionQueryReceived\n\trecursionNotDetected\n\trecursionDetected\n)\n\nvar DefaultExcludeSuffixes = client.DefaultExcludeSuffixes //nolint:gochecknoglobals // constant\n\ntype nsAndDomains struct {\n\tdomains   []string\n\tnamespace string\n}\n\n// Server is a DNS server which implements the github.com/miekg/dns Handler interface.\ntype Server struct {\n\tsync.RWMutex\n\tclient.DNS\n\tctx          context.Context // necessary to make logging work in ServeDNS function\n\tfallbackPool FallbackPool\n\trequestCount int64\n\tcache        *xsync.Map[cacheKey, *cacheEntry]\n\trecursive    int32 // one of the recursionXXX constants declared above (unique type avoided because it just gets messy with the atomic calls)\n\n\t// Suffixes to immediately drop from the query before processing. This list will always contain the tel2Search domain.\n\t// The overriding resolver will also add the search path found in /etc/resolv.conf, because that search path is not\n\t// intended for this resolver and will get reapplied when passing things on to the fallback resolver.\n\tdropSuffixes []string\n\n\t// routes are typically namespaces, accessible using <service-name>.<namespace-name>.\n\troutes map[string]struct{}\n\n\t// search are appended to a query to form new names that are then dispatched to the\n\t// DNS resolver. The act of appending is not performed by this server, but rather\n\t// by the system's DNS resolver before calling on this server.\n\tsearch []string\n\n\t// domains contains the sum of the include-suffixes and routes. It is currently only\n\t// used by the darwin resolver to keep track of files to add or remove.\n\tdomains map[string]struct{}\n\n\t// nsAndDomainsCh receives requests to change the top level domains and the search path.\n\tnsAndDomainsCh chan nsAndDomains\n\n\t// clusterDomain reported by the traffic-manager\n\tclusterDomain string\n\n\t// Function that resolves the request using the client's config and the cache.\n\tclientLookup func(*dns.Question) (dnsproxy.RRs, int, error)\n\n\t// Function that sends a lookup request to the traffic-manager\n\tclusterLookup Resolver\n\n\t// mappingsMap is contains the same mappings as DNS.Mappings but as a map (for performance).\n\tmappingsMap map[string]string\n\n\t// namespaceDomain is the current connected kubernetes namespace suffixed by a dot.\n\tnamespaceDomain string\n\n\terror string\n\n\treadyClose sync.Once\n\n\t// ready is closed when the DNS server is fully configured\n\tready chan struct{}\n}\n\ntype cacheEntry struct {\n\tcreated time.Time\n\tanswer  dnsproxy.RRs\n\trCode   int\n\twait    chan struct{}\n}\n\n// cacheTTL is the time to live for an entry in the local DNS cache.\nconst cacheTTL = 60 * time.Second\n\nfunc (dv *cacheEntry) expired() bool {\n\treturn time.Since(dv.created) > cacheTTL\n}\n\nfunc sliceToLower(ss []string) []string {\n\tfor i, s := range ss {\n\t\tss[i] = strings.ToLower(s)\n\t}\n\treturn ss\n}\n\n// NewServer returns a new dns.Server.\nfunc NewServer(config *client.DNS, namespace string, clusterLookup Resolver) *Server {\n\tif config == nil {\n\t\tconfig = &client.DNS{}\n\t}\n\tif len(config.ExcludeSuffixes) == 0 {\n\t\tconfig.ExcludeSuffixes = DefaultExcludeSuffixes\n\t}\n\tif config.LookupTimeout <= 0 {\n\t\tconfig.LookupTimeout = 4 * time.Second\n\t}\n\treturn &Server{\n\t\tDNS:             *config,\n\t\tmappingsMap:     mappingsMap(config.Mappings),\n\t\tcache:           xsync.NewMap[cacheKey, *cacheEntry](),\n\t\troutes:          make(map[string]struct{}),\n\t\tdomains:         make(map[string]struct{}),\n\t\tdropSuffixes:    []string{tel2SubDomainDot},\n\t\tsearch:          []string{client.Tel2SubDomain},\n\t\tnsAndDomainsCh:  make(chan nsAndDomains, 5),\n\t\tclusterDomain:   defaultClusterDomain,\n\t\tnamespaceDomain: namespace + \".\",\n\t\tclusterLookup:   clusterLookup,\n\t\tready:           make(chan struct{}),\n\t}\n}\n\n// tel2SubDomain helps differentiate between single label and qualified DNS queries.\n//\n// Dealing with single label names is tricky because what we really want is to receive the\n// name and then forward it verbatim to the DNS resolver in the cluster so that it can\n// add whatever search paths to it that it sees fit, but in order to receive single name\n// queries in the first place, our DNS resolver must have a search path that adds a domain\n// that the DNS system knows that we will handle.\n//\n// Example flow:\n// The user queries for the name \"alpha\". The DNS system on the host tries the search path\n// of our DNS resolver which contains \"tel2-search\" and creates the name \"alpha.tel2-search\".\n// The DNS system now discovers that our DNS resolver handles that domain, so we receive\n// the query. We then strip the \"tel2-search\" and send the original single label name to the\n// cluster, and we add it back before we forward the reply.\nconst (\n\ttel2SubDomainDot = client.Tel2SubDomain + \".\"\n)\n\n// excludePrefixes are prefixes for name queries that we just want to respond NXNAME to\n// without dispatching them to the cluster.\n//\n//nolint:gochecknoglobals // constant\nvar excludePrefixes = []string{\n\t\"wpad.\",\n\t\"_grpc_config.\",\n\t\"lb._dns-sd.\",\n\t\"db._dns-sd.\",\n\t\"b._dns-sd.\",\n\t\"win11-vb.\",\n\t\"dc.msdcs.\",\n\t\"_ldap._tcp.dc._msdcs.\",\n}\n\nvar (\n\tlocalhostIPv4 = net.IP{127, 0, 0, 1}                                   //nolint:gochecknoglobals // constant\n\tlocalhostIPv6 = net.IP{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} //nolint:gochecknoglobals // constant\n)\n\nfunc (s *Server) shouldDoClusterLookup(query string) bool {\n\tfor _, pf := range excludePrefixes {\n\t\tif strings.HasPrefix(query, pf) {\n\t\t\tclog.Debugf(s.ctx, `Cluster DNS excluded by exclude-prefix %q for name %q`, pf, query)\n\t\t\treturn false\n\t\t}\n\t}\n\tname := query[:len(query)-1] // skip last dot\n\n\tif s.isExcluded(name) {\n\t\t// Reject any host explicitly added to the exclude list.\n\t\tclog.Debugf(s.ctx, \"Cluster DNS explicitly excluded for name %q\", name)\n\t\treturn false\n\t}\n\n\tif !strings.ContainsRune(name, '.') {\n\t\t// Single label names are always included.\n\t\tclog.Debugf(s.ctx, \"Cluster DNS included for single label name %q\", name)\n\t\treturn true\n\t}\n\n\t// Skip configured exclude-suffixes unless also matched by an include-suffix\n\t// that is longer (i.e. more specific).\n\tsuffixExcluded := func(n string) (included, excluded bool) {\n\t\tfor _, es := range s.ExcludeSuffixes {\n\t\t\tif strings.HasSuffix(n, es) {\n\t\t\t\t// Exclude unless more specific include.\n\t\t\t\tfor _, is := range s.IncludeSuffixes {\n\t\t\t\t\tif len(is) >= len(es) && strings.HasSuffix(n, is) {\n\t\t\t\t\t\tclog.Debugf(s.ctx,\n\t\t\t\t\t\t\t\"Cluster DNS included by include-suffix %q (overriding exclude-suffix %q) for name %q\", is, es, n)\n\t\t\t\t\t\treturn true, false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tclog.Debugf(s.ctx, \"Cluster DNS excluded by exclude-suffix %q for name %q\", es, n)\n\t\t\t\treturn false, true\n\t\t\t}\n\t\t}\n\t\treturn false, false\n\t}\n\n\tif include, exclude := suffixExcluded(name); include || exclude {\n\t\treturn include\n\t}\n\n\t// Always include configured search paths\n\tln := len(name)\n\tfor _, sfx := range s.search {\n\t\tli := ln - len(sfx) - 1\n\t\tif li > 0 && name[li] == '.' && strings.HasSuffix(name, sfx) {\n\t\t\tif include, exclude := suffixExcluded(name[:li]); include || exclude {\n\t\t\t\treturn include\n\t\t\t}\n\t\t\tclog.Debugf(s.ctx, \"Cluster DNS included by search %q of name %q\", sfx, name)\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// Always include configured routes\n\tfor sfx := range s.routes {\n\t\tli := ln - len(sfx) - 1\n\t\tif li > 0 && name[li] == '.' && strings.HasSuffix(name, sfx) {\n\t\t\tif include, exclude := suffixExcluded(name[:li]); include || exclude {\n\t\t\t\treturn include\n\t\t\t}\n\t\t\tclog.Debugf(s.ctx, \"Cluster DNS included by namespace %q of name %q\", sfx, name)\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// Always include queries for the cluster domain.\n\tif strings.HasSuffix(query, \".\"+s.clusterDomain) {\n\t\tclog.Debugf(s.ctx, \"Cluster DNS included by cluster domain %q of name %q\", s.clusterDomain, name)\n\t\treturn true\n\t}\n\n\t// Always include configured includeSuffixes\n\tfor _, sfx := range s.IncludeSuffixes {\n\t\tif strings.HasSuffix(name, sfx) {\n\t\t\tif sfx[0] == '.' {\n\t\t\t\tif include, exclude := suffixExcluded(strings.TrimSuffix(name, sfx)); include || exclude {\n\t\t\t\t\treturn include\n\t\t\t\t}\n\t\t\t}\n\t\t\tclog.Debugf(s.ctx,\n\t\t\t\t\"Cluster DNS included by include-suffix %q for name %q\", sfx, name)\n\t\t\treturn true\n\t\t}\n\t}\n\n\t// Pass any queries for the cluster domain.\n\tclog.Debugf(s.ctx, \"Cluster DNS excluded for name %q. No inclusion rule was matched\", name)\n\treturn false\n}\n\nfunc (s *Server) isExcluded(name string) bool {\n\tif slice.Contains(s.Excludes, name) {\n\t\treturn true\n\t}\n\n\t// When intercepting, this function will potentially receive the hostname of any search param, so their\n\t// unqualified hostname should be evaluated too.\n\tqLen := len(name)\n\tfor _, sp := range s.search {\n\t\tif strings.HasSuffix(name, \".\"+sp) && slice.Contains(s.Excludes, name[:qLen-len(sp)-1]) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s *Server) isDomainExcluded(name string) bool {\n\treturn slices.Contains(s.ExcludeSuffixes, \".\"+name)\n}\n\nfunc (s *Server) resolveInCluster(c context.Context, q *dns.Question) (result dnsproxy.RRs, rCode int, err error) {\n\tquery := q.Name\n\tif query == \"localhost.\" {\n\t\t// BUG(lukeshu): I have no idea why a lookup\n\t\t// for localhost even makes it to here on my\n\t\t// home WiFi when connecting to a k3sctl\n\t\t// cluster (but not a kubernaut.io cluster).\n\t\t// But it does, so I need this in order to be\n\t\t// productive at home.  We should really\n\t\t// root-cause this, because it's weird.\n\t\thdr := dns.RR_Header{\n\t\t\tName:   q.Name,\n\t\t\tRrtype: q.Qtype,\n\t\t\tClass:  q.Qclass,\n\t\t}\n\t\tswitch q.Qtype {\n\t\tcase dns.TypeA:\n\t\t\treturn dnsproxy.RRs{&dns.A{\n\t\t\t\tHdr: hdr,\n\t\t\t\tA:   localhostIPv4,\n\t\t\t}}, dns.RcodeSuccess, nil\n\t\tcase dns.TypeAAAA:\n\t\t\treturn dnsproxy.RRs{&dns.AAAA{\n\t\t\t\tHdr:  hdr,\n\t\t\t\tAAAA: localhostIPv6,\n\t\t\t}}, dns.RcodeSuccess, nil\n\t\tdefault:\n\t\t\treturn nil, dns.RcodeNameError, nil\n\t\t}\n\t}\n\n\tif !s.shouldDoClusterLookup(query) {\n\t\treturn nil, dns.RcodeNameError, nil\n\t}\n\n\t// Give the cluster lookup a reasonable timeout.\n\tc, cancel := context.WithTimeout(c, s.LookupTimeout)\n\tdefer cancel()\n\n\tresult, rCode, err = s.clusterLookup(c, q)\n\tif err != nil {\n\t\tswitch {\n\t\tcase errors.Is(err, context.DeadlineExceeded), errors.Is(err, context.Canceled), status.Code(err) == codes.DeadlineExceeded, status.Code(err) == codes.Canceled:\n\t\t\trCode = dns.RcodeNameError\n\t\t\terr = nil\n\t\tdefault:\n\t\t\tclog.Errorf(s.ctx, \"Error resolving %q in cluster: %T %v\", query, err, err)\n\t\t}\n\t\treturn nil, rCode, client.CheckTimeout(c, err)\n\t}\n\n\t// Keep the TTLs of requests resolved in the cluster low. We\n\t// cache them locally anyway, but our cache is flushed when things are\n\t// intercepted or the namespaces change.\n\tfor _, rr := range result {\n\t\tif h := rr.Header(); h != nil {\n\t\t\th.Ttl = dnsTTL\n\t\t}\n\t}\n\treturn result, rCode, nil\n}\n\nfunc (s *Server) GetConfig() *client.DNS {\n\tvar d client.DNS\n\ts.RLock()\n\td = s.DNS\n\ts.RUnlock()\n\treturn &d\n}\n\nfunc (s *Server) Ready() <-chan struct{} {\n\treturn s.ready\n}\n\nfunc (s *Server) Stop() {\n\t// Close s.ready unless it's already closed\n\ts.readyClose.Do(func() { close(s.ready) })\n}\n\nfunc (s *Server) SetClusterDNS(dns *manager.DNS, vifDNS netip.AddrPort) {\n\ts.Lock()\n\tif !s.VIFAddress.IsValid() {\n\t\ts.VIFAddress = vifDNS\n\t}\n\tif dns != nil {\n\t\tif slices.Equal(s.ExcludeSuffixes, DefaultExcludeSuffixes) && len(dns.ExcludeSuffixes) > 0 {\n\t\t\ts.ExcludeSuffixes = sliceToLower(dns.ExcludeSuffixes)\n\t\t}\n\t\tif len(s.IncludeSuffixes) == 0 {\n\t\t\ts.IncludeSuffixes = sliceToLower(dns.IncludeSuffixes)\n\t\t}\n\t\ts.clusterDomain = strings.ToLower(dns.ClusterDomain)\n\t}\n\ts.Unlock()\n}\n\n// SetTopLevelDomainsAndSearchPath updates the DNS top level domains and the search path used by the resolver.\nfunc (s *Server) SetTopLevelDomainsAndSearchPath(ctx context.Context, domains []string, namespace string) {\n\tdas := nsAndDomains{\n\t\tdomains:   domains,\n\t\tnamespace: namespace,\n\t}\n\tselect {\n\tcase <-ctx.Done():\n\tcase s.nsAndDomainsCh <- das:\n\t}\n}\n\nfunc (s *Server) purgeRecordsFromCache(keyName string) {\n\tkeyName = strings.TrimSuffix(keyName, \".\") + \".\"\n\tfor _, qType := range []uint16{dns.TypeA, dns.TypeAAAA} {\n\t\ts.cache.Delete(cacheKey{name: keyName, qType: qType})\n\t}\n}\n\n// SetExcludes sets the excludes list in the config.\nfunc (s *Server) SetExcludes(excludes []string) {\n\tfor i, e := range excludes {\n\t\texcludes[i] = strings.ToLower(e)\n\t}\n\ts.Lock()\n\toldExcludes := s.Excludes\n\ts.Excludes = excludes\n\ts.Unlock()\n\n\tfor _, e := range slice.AppendUnique(oldExcludes, excludes...) {\n\t\ts.purgeRecordsFromCache(e)\n\t}\n}\n\nfunc mappingsMap(mappings []*client.DNSMapping) map[string]string {\n\tif l := len(mappings); l > 0 {\n\t\tmm := make(map[string]string, l)\n\t\tfor _, m := range mappings {\n\t\t\tal := m.AliasFor\n\t\t\tif _, err := netip.ParseAddr(al); err != nil {\n\t\t\t\tal += \".\"\n\t\t\t}\n\t\t\tmm[strings.ToLower(m.Name+\".\")] = strings.ToLower(al)\n\t\t}\n\t\treturn mm\n\t}\n\treturn nil\n}\n\n// SetMappings sets the Mappings list in the config.\nfunc (s *Server) SetMappings(mappings []*rpc.DNSMapping) {\n\tml := client.MappingsFromRPC(mappings)\n\tmm := mappingsMap(ml)\n\ts.Lock()\n\ts.Mappings = ml\n\ts.mappingsMap = mm\n\ts.Unlock()\n\n\t// Flush the mappings.\n\tfor n := range mm {\n\t\ts.purgeRecordsFromCache(n)\n\t}\n}\n\nfunc newLocalUDPListener(c context.Context) (net.PacketConn, error) {\n\tlc := &net.ListenConfig{}\n\treturn lc.ListenPacket(c, \"udp\", \"127.0.0.1:0\")\n}\n\nfunc (s *Server) processSearchPaths(g log.Group, processor func(context.Context, vif.Device) error, dev vif.Device) {\n\tg.Go(\"SearchPaths\", func(c context.Context) error {\n\t\tprevDas := nsAndDomains{\n\t\t\tdomains:   []string{},\n\t\t\tnamespace: \"\",\n\t\t}\n\t\tunchanged := func(das nsAndDomains) bool {\n\t\t\treturn das.namespace == prevDas.namespace && slices.Equal(das.domains, prevDas.domains)\n\t\t}\n\n\t\tfirst := true\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-c.Done():\n\t\t\t\treturn nil\n\t\t\tcase das := <-s.nsAndDomainsCh:\n\t\t\t\t// Only interested in the last one, and only if it differs\n\t\t\t\tif len(s.nsAndDomainsCh) > 0 || unchanged(das) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tprevDas = das\n\n\t\t\t\troutes := make(map[string]struct{}, len(das.domains))\n\t\t\t\tfor _, domain := range das.domains {\n\t\t\t\t\tif domain != \"\" && !s.isDomainExcluded(domain) {\n\t\t\t\t\t\troutes[domain] = struct{}{}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif !s.isDomainExcluded(\"svc\") {\n\t\t\t\t\troutes[\"svc\"] = struct{}{}\n\t\t\t\t}\n\t\t\t\ts.Lock()\n\t\t\t\ts.routes = routes\n\n\t\t\t\t// The connected namespace must be included as a search path for the cases\n\t\t\t\t// where it's up to the traffic-manager to resolve. It cannot resolve a single\n\t\t\t\t// label name intended for other namespaces.\n\t\t\t\ts.search = []string{client.Tel2SubDomain, das.namespace}\n\t\t\t\ts.Unlock()\n\n\t\t\t\tif err := processor(c, dev); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif first {\n\t\t\t\t\tfirst = false\n\t\t\t\t\ttime.AfterFunc(2*time.Second, func() {\n\t\t\t\t\t\ts.performRecursionCheck(c)\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc (s *Server) flushDNS() {\n\ts.cache.Clear()\n}\n\n// splitToUDPAddr splits the given address into an address and port. It's\n// an error if the address is based on a hostname rather than an IP.\nfunc splitToUDPAddr(netAddr net.Addr) (netip.AddrPort, error) {\n\treturn iputil.SplitToIPPort(netAddr)\n}\n\n// RequestCount returns the number of requests that this server has received.\nfunc (s *Server) RequestCount() int {\n\treturn int(atomic.LoadInt64(&s.requestCount))\n}\n\nfunc copyRRs(rrs dnsproxy.RRs, qTypes []uint16) (cp, others dnsproxy.RRs) {\n\tif len(rrs) == 0 {\n\t\treturn rrs, nil\n\t}\n\tcp = make(dnsproxy.RRs, 0, len(rrs))\n\tothers = make(dnsproxy.RRs, 0, len(rrs))\n\tfor _, rr := range rrs {\n\t\tif slice.Contains(qTypes, rr.Header().Rrtype) {\n\t\t\tcp = append(cp, dns.Copy(rr))\n\t\t} else {\n\t\t\tothers = append(others, dns.Copy(rr))\n\t\t}\n\t}\n\treturn cp, others\n}\n\ntype cacheKey struct {\n\tname  string\n\tqType uint16\n}\n\nfunc (c *cacheKey) String() string {\n\treturn fmt.Sprintf(\"%s %s\", dns.TypeToString[c.qType], c.name)\n}\n\nconst (\n\t// recursionCheck is a special host name in a well known namespace that isn't expected to exist. It\n\t// is used once for determining if the cluster's DNS resolver will call the Telepresence DNS resolver\n\t// recursively. This is common when the cluster is running on the local host (k3s in docker for instance).\n\t//\n\t// The check is performed using the following steps.\n\t// 1. A lookup is made for \"tel-recursion-check\n\t// 2. When our DNS-resolver receives this lookup, it modifies the name to \"tel2-recursion-check.kube-system.\"\n\t//    and sends that on to the cluster.\n\t// 3. If our DNS-resolver now encounters a query for the \"tel2-recursion-check.kube-system.\", then we know\n\t//    that a recursion took place.\n\t// 4. If no request for \"tel2-recursion-check.kube-system.\" is received, then it's assumed that the resolver\n\t//    is not recursive.\n\trecursionCheck = \"tel2-recursion-check.\"\n)\n\nfunc (s *Server) resolveWithRecursionCheck(q *dns.Question) (dnsproxy.RRs, int, error) {\n\tif strings.HasPrefix(q.Name, recursionCheck) {\n\t\tif atomic.CompareAndSwapInt32(&s.recursive, recursionQueryReceived, recursionDetected) {\n\t\t\tclog.Debug(s.ctx, \"DNS resolver is recursive\")\n\t\t\treturn nil, dns.RcodeNameError, nil\n\t\t}\n\n\t\tif atomic.CompareAndSwapInt32(&s.recursive, recursionQueryNotYetReceived, recursionQueryReceived) {\n\t\t\ttc, cancel := context.WithTimeout(s.ctx, recursionTestTimeout)\n\t\t\tgo func() {\n\t\t\t\tdefer cancel()\n\t\t\t\t_, _, _ = s.resolveInCluster(s.ctx, q) // We really don't care about the reply here.\n\t\t\t}()\n\t\t\t<-tc.Done()\n\n\t\t\t// When we've gotten the reply from the cluster, we know if recursion did occur.\n\t\t\tif atomic.CompareAndSwapInt32(&s.recursive, recursionQueryReceived, recursionNotDetected) {\n\t\t\t\tclog.Debug(s.ctx, \"DNS resolver is not recursive\")\n\t\t\t}\n\t\t}\n\t\treturn localHostReply(q), dns.RcodeSuccess, nil\n\t}\n\tif atomic.LoadInt32(&s.recursive) == recursionDetected {\n\t\trecursive := false\n\t\ts.cache.Range(func(key cacheKey, ce *cacheEntry) bool {\n\t\t\tif key.qType == q.Qtype && strings.HasPrefix(q.Name, key.name) {\n\t\t\t\tselect {\n\t\t\t\tcase <-ce.wait:\n\t\t\t\t// the cache entry is resolved.\n\t\t\t\tcase <-time.After(500 * time.Millisecond):\n\t\t\t\t\t// the cache entry is still in progress after some delay, i.e. it's currently\n\t\t\t\t\t// querying the cluster. It's very likely that this is a recursive call.\n\t\t\t\t\trecursive = true\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tif recursive {\n\t\t\tclog.Debugf(s.ctx, \"returning error for query %q: assumed to be recursive\", q.Name)\n\t\t\t// Do we know that the name is correct?\n\t\t\trCode := dns.RcodeNameError\n\t\t\tif s.isNameCachedWithSuccess(q) {\n\t\t\t\t// Return empty but successful\n\t\t\t\trCode = dns.RcodeSuccess\n\t\t\t}\n\t\t\treturn nil, rCode, nil\n\t\t}\n\t}\n\treturn s.resolveThruCache(q)\n}\n\nfunc (s *Server) isNameCachedWithSuccess(q *dns.Question) bool {\n\tcKey := cacheKey{qType: dns.TypeNone}\n\tswitch q.Qtype {\n\tcase dns.TypeAAAA:\n\t\tcKey.qType = dns.TypeA\n\tcase dns.TypeA:\n\t\tcKey.qType = dns.TypeAAAA\n\tdefault:\n\t\treturn false\n\t}\n\n\tnames := []string{q.Name}\n\tif strings.HasSuffix(q.Name, s.namespaceDomain) {\n\t\tnames = append(names, strings.TrimSuffix(q.Name, s.namespaceDomain))\n\t}\n\tfor _, name := range names {\n\t\tcKey.name = name\n\t\tclog.Debugf(s.ctx, \"checking if name %q is cached with success\", name)\n\t\tif aRec, ok := s.cache.Load(cKey); ok {\n\t\t\t// The name is OK, but there were no records\n\t\t\tselect {\n\t\t\tcase <-aRec.wait:\n\t\t\t\tclog.Debugf(s.ctx, \"found %q cached with %s\", name, dns.RcodeToString[aRec.rCode])\n\t\t\t\tif aRec.rCode == dns.RcodeSuccess {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// resolveThruCache resolves the given query by first performing a cache lookup. If a cached\n// entry is found that hasn't expired, it's returned. If not, this function will call\n// resolveQuery() to resolve and store in the case.\nfunc (s *Server) resolveThruCache(q *dns.Question) (answer dnsproxy.RRs, rCode int, err error) {\n\tkey := cacheKey{name: q.Name, qType: q.Qtype}\n\tfound := false\n\tdv, _ := s.cache.Compute(key, func(dv *cacheEntry, loaded bool) (newValue *cacheEntry, op xsync.ComputeOp) {\n\t\tif loaded && !dv.expired() {\n\t\t\tfound = true\n\t\t\treturn dv, xsync.CancelOp\n\t\t}\n\t\treturn &cacheEntry{wait: make(chan struct{}), created: time.Now(), rCode: -1}, xsync.UpdateOp\n\t})\n\tif found {\n\t\t<-dv.wait\n\t\treturn dv.answer, dv.rCode, nil\n\t}\n\tdefer close(dv.wait)\n\n\tanswer, rCode, err = s.resolveInCluster(s.ctx, q)\n\tif err != nil {\n\t\tclog.Debugf(s.ctx, \"cache lookup failed for %s %s: %v\", dns.TypeToString[q.Qtype], q.Name, err)\n\t\tif errors.Is(err, context.DeadlineExceeded) || status.Code(err) == codes.Canceled {\n\t\t\trCode = dns.RcodeNameError\n\t\t}\n\t}\n\n\tdefer func() {\n\t\tif err != nil || rCode != dns.RcodeSuccess && rCode != dns.RcodeNameError {\n\t\t\t// We don't cache other types of errors because they might be caused by recoverable network glitches.\n\t\t\ts.cache.Delete(key)\n\t\t}\n\t}()\n\n\tif rCode != dns.RcodeSuccess && s.isNameCachedWithSuccess(q) {\n\t\terr = nil\n\t\trCode = dns.RcodeSuccess\n\t}\n\n\tqTypes := []uint16{q.Qtype}\n\tif q.Qtype != dns.TypeCNAME {\n\t\t// Allow additional CNAME records if they are present.\n\t\tfor _, rr := range dv.answer {\n\t\t\tif rr.Header().Rrtype == dns.TypeCNAME {\n\t\t\t\tqTypes = append(qTypes, dns.TypeCNAME)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tanswer, others := copyRRs(answer, qTypes)\n\tanswer = answer.PruneEmptyNames()\n\tdv.answer = answer\n\tdv.rCode = rCode\n\tif rCode == dns.RcodeSuccess {\n\t\tfor _, rr := range others {\n\t\t\t// Those should be cached as well.\n\t\t\tqType := rr.Header().Rrtype\n\t\t\tkey := cacheKey{name: rr.Header().Name, qType: qType}\n\t\t\trrs := dnsproxy.RRs{}\n\t\t\tif !dnsproxy.IsEmptyName(rr) {\n\t\t\t\trrs = append(rrs, rr)\n\t\t\t}\n\t\t\ts.cache.Compute(key, func(oldValue *cacheEntry, loaded bool) (newValue *cacheEntry, op xsync.ComputeOp) {\n\t\t\t\tif loaded {\n\t\t\t\t\toldValue.answer = rrs\n\t\t\t\t\toldValue.rCode = rCode\n\t\t\t\t\treturn oldValue, xsync.CancelOp\n\t\t\t\t}\n\t\t\t\tce := &cacheEntry{wait: make(chan struct{}), created: time.Now(), answer: rrs, rCode: rCode}\n\t\t\t\tclose(ce.wait)\n\t\t\t\treturn ce, xsync.UpdateOp\n\t\t\t})\n\t\t}\n\t}\n\treturn answer, rCode, err\n}\n\n// dfs is a func that implements the fmt.Stringer interface. Used in log statements to ensure\n// that the function isn't evaluated until the log output is formatted (which will happen only\n// if the given loglevel is enabled).\ntype dfs func() string\n\nfunc (d dfs) String() string {\n\treturn d()\n}\n\nfunc (s *Server) performRecursionCheck(c context.Context) {\n\tif proc.RunningInContainer() || !client.GetConfig(c).DNS().RecursionCheck {\n\t\ts.readyClose.Do(func() { close(s.ready) })\n\t\treturn\n\t}\n\tdefer func() {\n\t\tclog.Debug(c, \"Recursion check finished\")\n\t\ts.readyClose.Do(func() { close(s.ready) })\n\t}()\n\trc := recursionCheck + client.Tel2SubDomain\n\tclog.Debugf(c, \"Performing initial recursion check with %s\", rc)\n\ti := 0\n\tatomic.StoreInt32(&s.recursive, recursionQueryNotYetReceived)\n\tfor ; c.Err() == nil && i < maxRecursionTestRetries && atomic.LoadInt32(&s.recursive) == recursionQueryNotYetReceived; i++ {\n\t\tgo func() {\n\t\t\t_, _ = net.DefaultResolver.LookupIP(c, \"ip4\", rc)\n\t\t}()\n\t\ttime.Sleep(500 * time.Millisecond)\n\t}\n\tif i == maxRecursionTestRetries {\n\t\tmsg := \"DNS doesn't seem to work properly\"\n\t\tclog.Error(c, msg)\n\t\ts.Lock()\n\t\ts.error = msg\n\t\ts.Unlock()\n\t\treturn\n\t}\n\t// Await result\n\tfor c.Err() == nil {\n\t\trc := atomic.LoadInt32(&s.recursive)\n\t\tif rc == recursionDetected || rc == recursionNotDetected {\n\t\t\tbreak\n\t\t}\n\t\ttime.Sleep(10 * time.Millisecond)\n\t}\n}\n\nfunc localHostReply(q *dns.Question) dnsproxy.RRs {\n\tswitch q.Qtype {\n\tcase dns.TypeA:\n\t\treturn dnsproxy.RRs{&dns.A{\n\t\t\tHdr: dns.RR_Header{\n\t\t\t\tName:   q.Name,\n\t\t\t\tRrtype: q.Qtype,\n\t\t\t\tClass:  q.Qclass,\n\t\t\t},\n\t\t\tA: localhostIPv4,\n\t\t}}\n\tcase dns.TypeAAAA:\n\t\treturn dnsproxy.RRs{&dns.AAAA{\n\t\t\tHdr: dns.RR_Header{\n\t\t\t\tName:   q.Name,\n\t\t\t\tRrtype: q.Qtype,\n\t\t\t\tClass:  q.Qclass,\n\t\t\t},\n\t\t\tAAAA: localhostIPv6,\n\t\t}}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ServeDNS is an implementation of github.com/miekg/dns Handler.ServeDNS.\nfunc (s *Server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) {\n\tc := s.ctx\n\tatomic.AddInt64(&s.requestCount, 1)\n\n\tq := &r.Question[0]\n\tqts := dns.TypeToString[q.Qtype]\n\tclog.Debugf(c, \"ServeDNS %5d %-6s %s\", r.Id, qts, q.Name)\n\n\tmsg := new(dns.Msg)\n\tvar pfx dfs = func() string { return \"\" }\n\tvar txt dfs = func() string { return \"\" }\n\tvar rct dfs = func() string { return dns.RcodeToString[msg.Rcode] }\n\n\tdefer func() {\n\t\tclog.Debugf(c, \"%s%5d %-6s %s -> %s %s\", pfx, r.Id, qts, q.Name, rct, txt)\n\t\t_ = w.WriteMsg(msg)\n\n\t\t// Closing the response tells the DNS service to terminate\n\t\tif c.Err() != nil {\n\t\t\t_ = w.Close()\n\t\t}\n\t}()\n\n\t// The sanity-check query is sent during the configuration phase of the DNS server and then\n\t// never again. It must reply with localhost.\n\t//\n\t// NOTE! The sanity-check will always use the tel2-search subdomain, so the check made here\n\t//       must be made before the tel2-search is removed.\n\tif q.Name == santiyCheckDot {\n\t\tanswer := localHostReply(q)\n\t\tif answer == nil {\n\t\t\tmsg.SetRcode(r, dns.RcodeNotImplemented)\n\t\t\treturn\n\t\t}\n\t\tmsg.SetReply(r)\n\t\tmsg.Answer = answer\n\t\tmsg.Authoritative = true\n\t\ttxt = func() string { return answer.String() }\n\t\tclog.Debug(c, \"sanity-check OK\")\n\t\treturn\n\t}\n\n\tif !dnsproxy.SupportedType(q.Qtype) {\n\t\tmsg.SetRcode(r, dns.RcodeNotImplemented)\n\t\treturn\n\t}\n\n\t// We make changes to the query name, so we better restore it prior to writing an\n\t// answer back, or the caller will get confused.\n\torigName := q.Name\n\tdefer func() {\n\t\tqs := msg.Question\n\t\tif len(qs) > 0 {\n\t\t\tmq := &qs[0] // Important to use a pointer here. We don't want to change a by-value copied struct.\n\t\t\tif mq.Name == q.Name {\n\t\t\t\tmq.Name = origName\n\t\t\t}\n\t\t}\n\t\tfor _, rr := range msg.Answer {\n\t\t\th := rr.Header()\n\t\t\tif h.Name == q.Name {\n\t\t\t\th.Name = origName\n\t\t\t}\n\t\t}\n\t\tq.Name = origName\n\t}()\n\n\t// We're all about lowercase in here\n\tq.Name = strings.ToLower(origName)\n\n\t// The tel2SubDomain serves one purpose and one purpose alone. It's there to coerce the\n\t// system DNS resolver to direct requests to this resolver. The system configuration to\n\t// make this happen varies depending on OS, but the purpose is always the same. Given that,\n\t// the first step in the resolution is to remove this domain-suffix if it exists.\n\tln := len(q.Name)\n\tfor _, dropSuffix := range s.dropSuffixes {\n\t\tif strings.HasSuffix(q.Name, dropSuffix) {\n\t\t\t// Remove the suffix and ensure that the name still ends\n\t\t\t// with a dot after the removal. If it doesn't, then this\n\t\t\t// was not really a domain suffix but rather a partial\n\t\t\t// domain name.\n\t\t\tn := q.Name[:ln-len(dropSuffix)]\n\t\t\tif last := len(n) - 1; last > 0 && n[last] == '.' {\n\t\t\t\tq.Name = n\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\n\tvar answer dnsproxy.RRs\n\tvar rCode int\n\tvar err error\n\n\tswitch q.Qtype {\n\tcase dns.TypeA, dns.TypeAAAA, dns.TypeCNAME:\n\t\tif strings.Contains(q.Name, tel2SubDomainDot) {\n\t\t\t// This is a bogus name because it has some domain after\n\t\t\t// the tel2-search domain. Should normally never happen, but\n\t\t\t// will happen if someone queries for the tel2-search domain\n\t\t\t// as a single label name.\n\t\t\tmsg.SetRcode(r, dns.RcodeNameError)\n\t\t\treturn\n\t\t}\n\n\t\t// try and resolve any mappings before consulting the cache, so that mapping hits don't\n\t\t// end up in the cache.\n\t\tanswer, rCode, err = s.resolveMapping(q)\n\t\tif err == errNoMapping {\n\t\t\tanswer, rCode, err = s.clientLookup(q)\n\t\t}\n\tcase dns.TypePTR:\n\t\t// Respond with cluster domain if the queried IP is the IP of this DNS server.\n\t\tif ip, err := dnsproxy.PtrAddress(q.Name); err == nil && ip == s.VIFAddress.Addr() {\n\t\t\tanswer = dnsproxy.RRs{\n\t\t\t\t&dns.PTR{\n\t\t\t\t\tHdr: dnsproxy.NewHeader(q.Name, q.Qtype),\n\t\t\t\t\tPtr: s.clusterDomain,\n\t\t\t\t},\n\t\t\t}\n\t\t\trCode = dns.RcodeSuccess\n\t\t\tbreak\n\t\t}\n\t\tfallthrough\n\tdefault:\n\t\tanswer, rCode, err = s.clientLookup(q)\n\t}\n\n\tif err == nil && rCode == dns.RcodeSuccess {\n\t\tmsg.SetReply(r)\n\t\tmsg.Answer = answer\n\t\tmsg.Authoritative = true\n\t\tmsg.RecursionAvailable = s.fallbackPool != nil\n\t\ttxt = func() string { return answer.String() }\n\t\treturn\n\t}\n\n\t// The recursion check query, or queries that end with the cluster domain name, are not dispatched to the\n\t// fallback DNS-server.\n\ts.RLock()\n\tcd := s.clusterDomain\n\ts.RUnlock()\n\tif s.fallbackPool == nil ||\n\t\tstrings.HasSuffix(q.Name, cd) ||\n\t\tstrings.HasSuffix(origName, tel2SubDomainDot) {\n\t\tif err != nil {\n\t\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\t\ttxt = func() string { return \"timeout\" }\n\t\t\t} else {\n\t\t\t\ttxt = err.Error\n\t\t\t}\n\t\t}\n\t\tmsg.SetRcode(r, rCode)\n\t} else {\n\t\t// Use the original query name when sending things to the fallback resolver.\n\t\tq.Name = origName\n\t\tpfx = func() string { return fmt.Sprintf(\"(%s) \", s.fallbackPool.RemoteAddr()) }\n\t\tmsg, txt = s.fallbackExchange(c, msg, r)\n\t}\n}\n\nfunc (s *Server) fallbackExchange(c context.Context, msg, r *dns.Msg) (*dns.Msg, func() string) {\n\tdc := &dns.Client{Net: \"udp\", Timeout: s.LookupTimeout}\n\tpoolMsg, _, err := s.fallbackPool.Exchange(c, dc, r)\n\tvar txt func() string\n\tif err != nil {\n\t\trCode := dns.RcodeServerFailure\n\t\ttxt = err.Error\n\t\tif netErr, ok := err.(net.Error); ok {\n\t\t\tswitch {\n\t\t\tcase netErr.Timeout():\n\t\t\t\ttxt = func() string { return \"timeout\" }\n\t\t\tcase netErr.Temporary(): //nolint:staticcheck // err.Temporary is deprecated\n\t\t\t\trCode = dns.RcodeRefused\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\tmsg.SetRcode(r, rCode)\n\t} else {\n\t\tmsg = poolMsg\n\t\tmsg.RecursionAvailable = true\n\t\ttxt = func() string { return dnsproxy.RRs(msg.Answer).String() }\n\t}\n\treturn msg, txt\n}\n\nvar errNoMapping = errors.New(\"no mapping\") //nolint:gochecknoglobals // constant\n\nfunc (s *Server) resolveMapping(q *dns.Question) (dnsproxy.RRs, int, error) {\n\tswitch q.Qtype {\n\tcase dns.TypeA, dns.TypeAAAA, dns.TypeCNAME:\n\tdefault:\n\t\treturn nil, dns.RcodeNameError, errNoMapping\n\t}\n\n\ts.RLock()\n\tmappingAlias, ok := s.mappingsMap[q.Name]\n\ts.RUnlock()\n\n\tif !ok {\n\t\treturn nil, dns.RcodeNameError, errNoMapping\n\t}\n\tif ip, err := netip.ParseAddr(mappingAlias); err == nil {\n\t\t// The name resolves to an A or AAAA record known by this DNS server.\n\t\tvar rrs dnsproxy.RRs\n\t\tif q.Qtype == dns.TypeA && ip.Is4() {\n\t\t\trrs = dnsproxy.RRs{&dns.A{\n\t\t\t\tHdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: dnsTTL},\n\t\t\t\tA:   ip.AsSlice(),\n\t\t\t}}\n\t\t} else if q.Qtype == dns.TypeAAAA && ip.Is6() {\n\t\t\trrs = dnsproxy.RRs{&dns.AAAA{\n\t\t\t\tHdr:  dns.RR_Header{Name: q.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: dnsTTL},\n\t\t\t\tAAAA: ip.AsSlice(),\n\t\t\t}}\n\t\t}\n\t\treturn rrs, dns.RcodeSuccess, nil\n\t}\n\n\tcnameRRs := dnsproxy.RRs{&dns.CNAME{\n\t\tHdr:    dns.RR_Header{Name: q.Name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: dnsTTL},\n\t\tTarget: mappingAlias,\n\t}}\n\n\tif q.Qtype == dns.TypeCNAME {\n\t\t// A query for the CNAME must only return the CNAME.\n\t\treturn cnameRRs, dns.RcodeSuccess, nil\n\t}\n\n\t// A query for an A or AAAA must resolve the CNAME and then return both the result and the\n\t// CNAME that resolved to it.\n\tanswer, rCode, err := s.clientLookup(&dns.Question{\n\t\tName:   mappingAlias,\n\t\tQtype:  q.Qtype,\n\t\tQclass: q.Qclass,\n\t})\n\tif err == nil {\n\t\tanswer = append(cnameRRs, answer...)\n\t}\n\treturn answer, rCode, err\n}\n\n// Run starts the DNS server(s) and waits for them to end.\nfunc (s *Server) Run(c context.Context, initDone chan<- struct{}, listeners []net.PacketConn, fallbackPool FallbackPool) error {\n\ts.ctx = c\n\ts.fallbackPool = fallbackPool\n\tif client.GetConfig(c).DNS().RecursionCheck {\n\t\ts.clientLookup = s.resolveWithRecursionCheck\n\t} else {\n\t\ts.clientLookup = s.resolveThruCache\n\t}\n\n\tg := log.NewGroup(c)\n\tfor _, listener := range listeners {\n\t\tsrv := &dns.Server{PacketConn: listener, Handler: s, ReadTimeout: time.Second}\n\t\tg.Go(listener.LocalAddr().String(), func(c context.Context) error {\n\t\t\tgo func() {\n\t\t\t\t<-c.Done()\n\t\t\t\tclog.Debugf(c, \"Shutting down DNS server\")\n\t\t\t\t_ = srv.ShutdownContext(c)\n\t\t\t}()\n\t\t\treturn srv.ActivateAndServe()\n\t\t})\n\t}\n\tclose(initDone)\n\treturn g.Wait()\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/server_darwin.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dnsproxy\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\nconst (\n\tmaxRecursionTestRetries = 10\n\trecursionTestTimeout    = 500 * time.Millisecond\n)\n\n// Worker places a file under the /etc/resolver directory so that it is picked up by the\n// macOS resolver. The file is configured with a single nameserver that points to the local IP\n// that the Telepresence DNS server listens to. The file is removed, and the DNS is flushed when\n// the worker terminates\n//\n// For more information about /etc/resolver files, please view the man pages available at\n//\n//\tman 5 resolver\n//\n// or, if not on a Mac, follow this link: https://www.manpagez.com/man/5/resolver/\nfunc (s *Server) Worker(c context.Context, dev vif.Device, configureDNS func(netip.AddrPort, netip.AddrPort)) error {\n\tresolverDirName := filepath.Join(\"/etc\", \"resolver\")\n\n\tlistener, err := newLocalUDPListener(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdnsAddr, err := splitToUDPAddr(listener.LocalAddr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tconfigureDNS(netip.AddrPort{}, dnsAddr)\n\n\terr = os.MkdirAll(resolverDirName, 0o755)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Ensure lingering all telepresence.* files are removed.\n\tif err := s.removeResolverFiles(c, resolverDirName); err != nil {\n\t\treturn err\n\t}\n\n\tdefer func() {\n\t\t_ = s.removeResolverFiles(c, resolverDirName)\n\t\ts.flushDNS()\n\t}()\n\n\t// Start local DNS server\n\tg := log.NewGroup(c)\n\tg.Go(\"Server\", func(c context.Context) error {\n\t\tif err := s.updateResolverFiles(c, resolverDirName, dnsAddr); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.processSearchPaths(g, func(c context.Context, _ vif.Device) error {\n\t\t\treturn s.updateResolverFiles(c, resolverDirName, dnsAddr)\n\t\t}, dev)\n\t\t// Server will close the listener, so no need to close it here.\n\t\treturn s.Run(c, make(chan struct{}), []net.PacketConn{listener}, nil)\n\t})\n\treturn g.Wait()\n}\n\n// removeResolverFiles performs rm -f /etc/resolver/telepresence.*.\nfunc (s *Server) removeResolverFiles(c context.Context, resolverDirName string) error {\n\tfiles, err := os.ReadDir(resolverDirName)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, file := range files {\n\t\tif n := file.Name(); strings.HasPrefix(n, \"telepresence.\") {\n\t\t\tfn := filepath.Join(resolverDirName, n)\n\t\t\tclog.Debugf(c, \"Removing file %q\", fn)\n\t\t\tif err := os.Remove(fn); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *Server) updateResolverFiles(c context.Context, resolverDirName string, dnsAddr netip.AddrPort) error {\n\ts.Lock()\n\tdefer s.Unlock()\n\n\tnewDomainResolveFile := func(domain string) *dnsproxy.ResolveFile {\n\t\treturn &dnsproxy.ResolveFile{\n\t\t\tPort:        int(dnsAddr.Port()),\n\t\t\tDomain:      domain,\n\t\t\tNameservers: []string{dnsAddr.Addr().String()},\n\t\t}\n\t}\n\n\t// All routes and include suffixes become domains\n\tdomains := make(map[string]*dnsproxy.ResolveFile, len(s.routes)+len(s.IncludeSuffixes))\n\tfor route := range s.routes {\n\t\tdomains[route] = newDomainResolveFile(route)\n\t}\n\tfor _, sfx := range s.IncludeSuffixes {\n\t\tsfx = strings.TrimPrefix(sfx, \".\")\n\t\tdomains[sfx] = newDomainResolveFile(sfx)\n\t}\n\tclusterDomain := strings.TrimSuffix(s.clusterDomain, \".\")\n\tdomains[clusterDomain] = newDomainResolveFile(clusterDomain)\n\tdomains[client.Tel2SubDomain] = newDomainResolveFile(client.Tel2SubDomain)\n\nnextSearch:\n\tfor _, search := range s.search {\n\t\tsearch = strings.TrimSuffix(search, \".\")\n\t\tif df, ok := domains[search]; ok {\n\t\t\tdf.Search = append(df.Search, search)\n\t\t\tcontinue\n\t\t}\n\t\tfor domain, df := range domains {\n\t\t\tif strings.HasSuffix(search, \".\"+domain) {\n\t\t\t\tdf.Search = append(df.Search, search)\n\t\t\t\tcontinue nextSearch\n\t\t\t}\n\t\t}\n\t}\n\n\tfor domain := range s.domains {\n\t\tif _, ok := domains[domain]; !ok {\n\t\t\tnsFile := domainResolverFile(resolverDirName, domain)\n\t\t\tclog.Infof(c, \"Removing %s\", nsFile)\n\t\t\tif err := os.Remove(nsFile); err != nil {\n\t\t\t\tclog.Error(c, err)\n\t\t\t}\n\t\t\tdelete(s.domains, domain)\n\t\t}\n\t}\n\n\tfor domain, rf := range domains {\n\t\tnsFile := domainResolverFile(resolverDirName, domain)\n\t\tif _, ok := s.domains[domain]; ok {\n\t\t\tif oldRf, err := dnsproxy.ReadResolveFile(nsFile); err != nil && rf.Equals(oldRf) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tclog.Infof(c, \"Regenerating %s\", nsFile)\n\t\t} else {\n\t\t\ts.domains[domain] = struct{}{}\n\t\t\tclog.Infof(c, \"Generating %s\", nsFile)\n\t\t}\n\t\tif err := rf.Write(nsFile); err != nil {\n\t\t\tclog.Error(c, err)\n\t\t}\n\t}\n\ts.flushDNS()\n\treturn nil\n}\n\nfunc domainResolverFile(resolverDirName, domain string) string {\n\treturn filepath.Join(resolverDirName, \"telepresence.\"+domain)\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/server_linux.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dnsproxy\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/forwarder\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\nconst (\n\tmaxRecursionTestRetries = 10\n\n\t// We use a fairly short delay here because if DNS recursion is a thing, then the cluster's DNS-server\n\t// has access to the caller host's network, so it runs locally in a Docker container or similar.\n\trecursionTestTimeout = 200 * time.Millisecond\n)\n\nvar errResolveDNotConfigured = errors.New(\"resolved not configured\")\n\nfunc (s *Server) Worker(c context.Context, dev vif.Device, configureDNS func(netip.AddrPort, netip.AddrPort)) error {\n\tif proc.RunningInContainer() {\n\t\t// Don't bother with systemd-resolved when running in a docker container\n\t\treturn s.runContainerServer(c, dev, configureDNS)\n\t}\n\n\terr := s.tryResolveD(clog.WithGroup(c, \"resolved\"), dev, configureDNS)\n\tif err == errResolveDNotConfigured {\n\t\terr = nil\n\t\tif c.Err() == nil {\n\t\t\tclog.Info(c, \"Unable to use systemd-resolved, falling back to local server\")\n\t\t\terr = s.runOverridingServer(clog.WithGroup(c, \"legacy\"), dev, configureDNS)\n\t\t}\n\t}\n\treturn err\n}\n\nfunc addressFromResolvConf(c context.Context) (ap netip.AddrPort, err error) {\n\tvar rf *dnsproxy.ResolveFile\n\trf, err = dnsproxy.ReadResolveFile(\"/etc/resolv.conf\")\n\tif err != nil {\n\t\treturn ap, err\n\t}\n\tclog.Debug(c, rf.String())\n\tif len(rf.Nameservers) > 0 {\n\t\tnsAddr := rf.Nameservers[0]\n\t\taddr, err := netip.ParseAddr(nsAddr)\n\t\tif err != nil {\n\t\t\treturn ap, fmt.Errorf(\"nameserver IP %q in /etc/resolv.conf is invalid: %v\", nsAddr, err)\n\t\t}\n\t\tp := rf.Port\n\t\tif p == 0 {\n\t\t\tp = 53\n\t\t}\n\t\tap = netip.AddrPortFrom(addr, uint16(p))\n\t}\n\treturn ap, nil\n}\n\nfunc (s *Server) runOverridingServer(c context.Context, dev vif.Device, configureDNS func(netip.AddrPort, netip.AddrPort)) error {\n\tif len(s.LocalAddresses) == 0 {\n\t\tap, err := addressFromResolvConf(c)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.LocalAddresses = []netip.AddrPort{ap}\n\t\tclog.Infof(c, \"Automatically set dns=%s\", ap)\n\t}\n\tif len(s.LocalAddresses) == 0 {\n\t\treturn errors.New(\"couldn't determine dns ip from /etc/resolv.conf\")\n\t}\n\n\tlisteners, err := s.dnsListeners(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdnsResolverAddr, err := splitToUDPAddr(listeners[0].LocalAddr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tclog.Debugf(c, \"Bootstrapping local DNS server on port %d\", dnsResolverAddr.Port())\n\n\t// Create the connection pool later used for fallback. We need to create this before the firewall\n\t// rule because the rule must exclude the local address of this connection in order to\n\t// let it reach the original destination and not cause an endless loop.\n\tpool, err := NewConnPool(s.LocalAddresses[0], 10)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tpool.Close()\n\t}()\n\n\tserverStarted := make(chan struct{})\n\tserverDone := make(chan struct{})\n\tg := log.NewGroup(c)\n\tg.Go(\"Server\", func(c context.Context) error {\n\t\tdefer close(serverDone)\n\t\t// The server will close the listener, so no need to close it here.\n\t\ts.processSearchPaths(g, func(c context.Context, _ vif.Device) error {\n\t\t\ts.flushDNS()\n\t\t\treturn nil\n\t\t}, dev)\n\t\treturn s.Run(c, serverStarted, listeners, pool)\n\t})\n\n\tconfigureDNS(s.VIFAddress, dnsResolverAddr)\n\n\tif proc.RunningInContainer() {\n\t\tg.Go(\"Local DNS\", func(c context.Context) error {\n\t\t\tselect {\n\t\t\tcase <-c.Done():\n\t\t\tcase <-serverStarted:\n\t\t\t\t// Give DNS server time to start before rerouting NAT\n\t\t\t\ttime.Sleep(time.Millisecond)\n\n\t\t\t\tlc := net.ListenConfig{}\n\t\t\t\tpc, err := lc.ListenPacket(c, \"udp\", \":53\")\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\tgo func() {\n\t\t\t\t\tif err = forwarder.ForwardUDP(c, tunnel.ClientToDNS, pc.(*net.UDPConn), dnsResolverAddr); err != nil {\n\t\t\t\t\t\tclog.Error(c, err)\n\t\t\t\t\t}\n\t\t\t\t}()\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tg.Go(\"NAT-redirect\", func(c context.Context) error {\n\t\tselect {\n\t\tcase <-c.Done():\n\t\tcase <-serverStarted:\n\t\t\t// Give DNS server time to start before rerouting NAT\n\t\t\ttime.Sleep(time.Millisecond)\n\n\t\t\terr := routeDNS(c, s.LocalAddresses[0], dnsResolverAddr, pool.LocalAddrs())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer func() {\n\t\t\t\tc := context.Background()\n\t\t\t\tunrouteDNS(c)\n\t\t\t\ts.flushDNS()\n\t\t\t}()\n\t\t\ts.flushDNS()\n\t\t\t<-serverDone // Stay alive until DNS server is done\n\t\t}\n\t\treturn nil\n\t})\n\treturn g.Wait()\n}\n\nfunc (s *Server) runContainerServer(c context.Context, dev vif.Device, configureDNS func(netip.AddrPort, netip.AddrPort)) error {\n\tlc := &net.ListenConfig{}\n\tl, err := lc.ListenPacket(c, \"udp\", \":53\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdnsResolverAddr, err := splitToUDPAddr(l.LocalAddr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tclog.Debugf(c, \"Bootstrapping local DNS server on port %d\", dnsResolverAddr.Port())\n\n\tap, err := addressFromResolvConf(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclog.Debugf(c, \"Using DNS fallback=%s\", ap)\n\tpool, err := NewConnPool(ap, 10)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tpool.Close()\n\t}()\n\n\tclog.Debugf(c, \"Bootstrapping local DNS server on port %d\", dnsResolverAddr.Port())\n\tserverStarted := make(chan struct{})\n\tserverDone := make(chan struct{})\n\tg := log.NewGroup(c)\n\tg.Go(\"Server\", func(c context.Context) error {\n\t\tdefer close(serverDone)\n\t\t// The server will close the listener, so no need to close it here.\n\t\ts.processSearchPaths(g, func(c context.Context, _ vif.Device) error {\n\t\t\ts.flushDNS()\n\t\t\treturn nil\n\t\t}, dev)\n\t\treturn s.Run(c, serverStarted, []net.PacketConn{l}, pool)\n\t})\n\n\tconfigureDNS(s.VIFAddress, dnsResolverAddr)\n\treturn g.Wait()\n}\n\nfunc (s *Server) dnsListeners(c context.Context) ([]net.PacketConn, error) {\n\tlistener, err := newLocalUDPListener(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []net.PacketConn{listener}, nil\n}\n\n// runNatTableCmd runs \"iptables -t nat ...\".\nfunc runNatTableCmd(c context.Context, args ...string) error {\n\t// We specifically don't want to use the cancellation of 'ctx' here, because we don't ever\n\t// want to leave things in a half-cleaned-up state.\n\tc = context.WithoutCancel(c)\n\targs = append([]string{\"-t\", \"nat\"}, args...)\n\tcmd := exec.CommandContext(c, \"iptables\", args...)\n\tif clog.Enabled(c, clog.LevelTrace) {\n\t\tclog.Trace(c, shellquote.ShellString(\"iptables\", args))\n\t}\n\treturn cmd.Run()\n}\n\nconst tpDNSChain = \"TELEPRESENCE_DNS\"\n\n// routeDNS creates a new chain in the \"nat\" table with two rules in it. One rule ensures\n// that all packets sent to the currently configured DNS service are rerouted to our local\n// DNS service. Another rule ensures that when our local DNS service cannot resolve and\n// uses a fallback, that fallback reaches the original DNS service.\nfunc routeDNS(c context.Context, dnsAddress netip.AddrPort, toAddr netip.AddrPort, localDNSs []netip.AddrPort) (err error) {\n\t// create the chain\n\tunrouteDNS(c)\n\n\t// Create the TELEPRESENCE_DNS chain\n\tif err = runNatTableCmd(c, \"-N\", tpDNSChain); err != nil {\n\t\treturn err\n\t}\n\n\t// This rule prevents that any rules in this table applies to the localDNS address when\n\t// used as a source. I.e. we let the local DNS server reach the original DNS server\n\tfor _, localDNS := range localDNSs {\n\t\tif err = runNatTableCmd(c, \"-A\", tpDNSChain,\n\t\t\t\"-p\", \"udp\",\n\t\t\t\"--source\", localDNS.Addr().String(),\n\t\t\t\"--sport\", strconv.Itoa(int(localDNS.Port())),\n\t\t\t\"-j\", \"RETURN\",\n\t\t); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\t// This rule redirects all packets intended for the DNS service to our local DNS service\n\tif err = runNatTableCmd(c, \"-A\", tpDNSChain,\n\t\t\"-p\", \"udp\",\n\t\t\"--dest\", dnsAddress.Addr().String()+\"/32\",\n\t\t\"--dport\", strconv.Itoa(int(dnsAddress.Port())),\n\t\t\"-j\", \"DNAT\",\n\t\t\"--to-destination\", toAddr.String(),\n\t); err != nil {\n\t\treturn err\n\t}\n\n\t// Alter locally generated packets before routing\n\treturn runNatTableCmd(c, \"-I\", \"OUTPUT\", \"1\", \"-j\", tpDNSChain)\n}\n\n// unrouteDNS removes the chain installed by routeDNS.\nfunc unrouteDNS(c context.Context) {\n\t// The errors returned by these commands aren't of any interest besides logging.\n\t_ = runNatTableCmd(c, \"-D\", \"OUTPUT\", \"-j\", tpDNSChain)\n\t_ = runNatTableCmd(c, \"-F\", tpDNSChain)\n\t_ = runNatTableCmd(c, \"-X\", tpDNSChain)\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/server_test.go",
    "content": "package dns\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/suite\"\n\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n)\n\ntype suiteServer struct {\n\tsuite.Suite\n\n\tserver *Server\n}\n\nfunc (s *suiteServer) SetupSuite() {\n\ts.server = &Server{\n\t\tcache: xsync.NewMap[cacheKey, *cacheEntry](),\n\t}\n}\n\nfunc (s *suiteServer) TestSetMappings() {\n\t// given\n\tentry := &cacheEntry{wait: make(chan struct{}), created: time.Now()}\n\taliasKeyA := cacheKey{name: \"echo-easy-alias.\", qType: dns.TypeA}\n\taliasKeyAAAA := cacheKey{name: \"echo-easy-alias.\", qType: dns.TypeAAAA}\n\taliasedToKeyA := cacheKey{name: \"echo-easy.blue.svc.cluster.local.\", qType: dns.TypeA}\n\taliasedToKeyAAAA := cacheKey{name: \"echo-easy.blue.svc.cluster.local.\", qType: dns.TypeA}\n\n\ts.server.cache.Store(aliasKeyA, entry)\n\ts.server.cache.Store(aliasKeyAAAA, entry)\n\ts.server.cache.Store(aliasedToKeyA, entry)\n\ts.server.cache.Store(aliasedToKeyAAAA, entry)\n\n\ts.server.mappingsMap = map[string]string{}\n\n\t// when\n\ts.server.SetMappings([]*rpc.DNSMapping{\n\t\t{\n\t\t\tName:     \"echo-easy-alias\",\n\t\t\tAliasFor: \"echo-easy.blue.svc.cluster.local\",\n\t\t},\n\t})\n\n\t// then\n\t_, exists := s.server.cache.Load(aliasKeyA)\n\ts.False(exists, \"Mapping's A record wasn't purged\")\n\t_, exists = s.server.cache.Load(aliasKeyAAAA)\n\ts.False(exists, \"Mapping's AAAA record wasn't purged\")\n\t_, exists = s.server.cache.Load(aliasedToKeyA)\n\ts.True(exists, \"Service's A record was purged\")\n\t_, exists = s.server.cache.Load(aliasedToKeyAAAA)\n\ts.True(exists, \"Service's AAAA record was purged\")\n\n\ts.Equal(s.server.mappingsMap, map[string]string{\n\t\t\"echo-easy-alias.\": \"echo-easy.blue.svc.cluster.local.\",\n\t})\n\n\t// given\n\ts.server.cache.Store(aliasKeyA, entry)\n\ts.server.cache.Store(aliasKeyAAAA, entry)\n\n\t// when\n\ts.server.SetMappings([]*rpc.DNSMapping{})\n\n\t// then\n\t// mappings are empty\n\ts.Empty(s.server.mappingsMap)\n\n\t// nothing is purged when clearing the mappings because mappings never make it to the cache\n\t_, exists = s.server.cache.Load(aliasKeyA)\n\ts.True(exists, \"Mapping's A record was purged\")\n\t_, exists = s.server.cache.Load(aliasKeyAAAA)\n\ts.True(exists, \"Mapping's AAAA record was purged\")\n\t_, exists = s.server.cache.Load(aliasedToKeyA)\n\ts.True(exists, \"Service's A record was purged\")\n\t_, exists = s.server.cache.Load(aliasedToKeyAAAA)\n\ts.True(exists, \"Service's AAAA record was purged\")\n}\n\nfunc (s *suiteServer) TestSetExcludes() {\n\t// given\n\tentry := &cacheEntry{wait: make(chan struct{}), created: time.Now()}\n\ttoDeleteARecordKey := cacheKey{name: \"echo-easy.\", qType: dns.TypeA}\n\ttoDelete4ARecordKey := cacheKey{name: \"echo-easy.\", qType: dns.TypeAAAA}\n\ttoDeleteNewARecordKey := cacheKey{name: \"new-excluded.\", qType: dns.TypeAAAA}\n\n\ts.server.cache.Store(toDeleteARecordKey, entry)\n\ts.server.cache.Store(toDelete4ARecordKey, entry)\n\ts.server.cache.Store(toDeleteNewARecordKey, entry)\n\n\ts.server.Excludes = []string{\"echo-easy\"}\n\n\t// when\n\tnewExcluded := []string{\"new-excluded\"}\n\ts.server.SetExcludes(newExcluded)\n\n\t// then\n\t_, exists := s.server.cache.Load(toDeleteARecordKey)\n\tassert.False(s.T(), exists, \"Excluded A record was purged\")\n\t_, exists = s.server.cache.Load(toDelete4ARecordKey)\n\tassert.False(s.T(), exists, \"Excluded AAAA record was purged\")\n\t_, exists = s.server.cache.Load(toDeleteNewARecordKey)\n\tassert.False(s.T(), exists, \"New excluded record was purged\")\n\tassert.Equal(s.T(), newExcluded, s.server.Excludes)\n}\n\nfunc (s *suiteServer) TestIsExcluded() {\n\t// given\n\ts.server.Excludes = []string{\n\t\t\"echo-easy\",\n\t}\n\ts.server.search = []string{\n\t\ttel2SubDomainDot + \"cluster.local\",\n\t\t\"blue.svc.cluster.local\",\n\t}\n\n\t// when & then\n\tassert.True(s.T(), s.server.isExcluded(\"echo-easy\"))\n\tassert.True(s.T(), s.server.isExcluded(\"echo-easy.tel2-search.cluster.local\"))\n\tassert.True(s.T(), s.server.isExcluded(\"echo-easy.blue.svc.cluster.local\"))\n\tassert.False(s.T(), s.server.isExcluded(\"something-else\"))\n}\n\nfunc TestServerTestSuite(t *testing.T) {\n\tsuite.Run(t, new(suiteServer))\n}\n"
  },
  {
    "path": "pkg/client/rootd/dns/server_windows.go",
    "content": "package dns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\nconst (\n\tmaxRecursionTestRetries = 40\n\trecursionTestTimeout    = 1500 * time.Millisecond\n)\n\nfunc (s *Server) Worker(c context.Context, dev vif.Device, configureDNS func(netip.AddrPort, netip.AddrPort)) error {\n\tlistener, err := newLocalUDPListener(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdnsAddr, err := splitToUDPAddr(listener.LocalAddr())\n\tif err != nil {\n\t\treturn err\n\t}\n\tconfigureDNS(s.VIFAddress, dnsAddr)\n\n\tvar pool FallbackPool\n\tif client.GetConfig(c).OSSpecific().Network.DNSWithFallback {\n\t\t// Create the connection pool later used for fallback.\n\t\tdnsServers, err := getDNSServerList()\n\t\tif err != nil {\n\t\t\tclog.Warnf(c, \"Failed to get DNS servers: %v\", err)\n\t\t} else {\n\t\t\tfor _, dnsServer := range dnsServers {\n\t\t\t\taddr, err := netip.ParseAddr(dnsServer)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Warn(c, err)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tp, err := NewConnPool(netip.AddrPortFrom(addr, 53), 10)\n\t\t\t\tif err == nil {\n\t\t\t\t\tclog.Infof(c, \"Using fallback DNS server: %s\", dnsServer)\n\t\t\t\t\tpool = p\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tclog.Warn(c, err)\n\t\t\t}\n\t\t\tif pool == nil {\n\t\t\t\tclog.Warnf(c, \"No viable fallback DNS server found\")\n\t\t\t} else {\n\t\t\t\tdefer pool.Close()\n\t\t\t}\n\t\t}\n\t}\n\n\t// Start local DNS server\n\tg := log.NewGroup(c)\n\tg.Go(\"Server\", func(c context.Context) error {\n\t\t// No need to close listener. It's closed by the dns server.\n\t\tdefer func() {\n\t\t\tc, cancel := context.WithTimeout(context.WithoutCancel(c), 5*time.Second)\n\t\t\ts.Lock()\n\t\t\t_ = dev.SetDNS(c, s.clusterDomain, s.VIFAddress, nil)\n\t\t\ts.Unlock()\n\t\t\tcancel()\n\t\t}()\n\t\tif err := s.updateRouterDNS(c, dev); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.processSearchPaths(g, s.updateRouterDNS, dev)\n\t\treturn s.Run(c, make(chan struct{}), []net.PacketConn{listener}, pool)\n\t})\n\treturn g.Wait()\n}\n\nfunc (s *Server) updateRouterDNS(c context.Context, dev vif.Device) error {\n\ts.Lock()\n\terr := dev.SetDNS(c, s.clusterDomain, s.VIFAddress, s.search)\n\ts.Unlock()\n\ts.flushDNS()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set DNS: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc getDNSServerList() ([]string, error) {\n\tiphlpapi := windows.NewLazyDLL(\"iphlpapi.dll\")\n\tgetNetworkParams := iphlpapi.NewProc(\"GetNetworkParams\")\n\n\t// First, call GetNetworkParams with a nil buffer to get the required buffer size\n\tvar bufferSize uint32\n\tret, _, _ := getNetworkParams.Call(uintptr(unsafe.Pointer(nil)), uintptr(unsafe.Pointer(&bufferSize)))\n\tif ret != uintptr(windows.ERROR_BUFFER_OVERFLOW) {\n\t\treturn nil, windows.Errno(ret)\n\t}\n\n\t// Allocate the required buffer size\n\tbuffer := make([]byte, bufferSize)\n\n\t// Call GetNetworkParams with the allocated buffer\n\tret, _, _ = getNetworkParams.Call(uintptr(unsafe.Pointer(&buffer[0])), uintptr(unsafe.Pointer(&bufferSize)))\n\tif ret != 0 {\n\t\treturn nil, windows.Errno(ret)\n\t}\n\n\t// Define the FIXED_INFO structure\n\ttype FIXED_INFO struct {\n\t\tHostName         [132]byte\n\t\tDomainName       [132]byte\n\t\tCurrentDNSServer *windows.IpAddrString\n\t\tDNSServerList    windows.IpAddrString\n\t\tNodeType         uint32\n\t\tScopeID          [260]byte\n\t\tEnableRouting    uint32\n\t\tEnableProxy      uint32\n\t\tEnableDNS        uint32\n\t}\n\n\t// Convert buffer to FIXED_INFO structure\n\tfi := (*FIXED_INFO)(unsafe.Pointer(&buffer[0]))\n\n\t// Traverse the DNS server list\n\tsl := fi.DNSServerList\n\tvar svcs []string\n\tfor {\n\t\tsvcs = append(svcs, windows.BytePtrToString(&sl.IpAddress.String[0]))\n\t\tif sl.Next == nil {\n\t\t\tbreak\n\t\t}\n\t\tsl = *sl.Next\n\t}\n\treturn svcs, nil\n}\n"
  },
  {
    "path": "pkg/client/rootd/grpc.go",
    "content": "package rootd\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/server\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nfunc (s *service) Version(ctx context.Context, _ *emptypb.Empty) (*common.VersionInfo, error) {\n\treturn client.VersionInfo(ctx), nil\n}\n\nfunc (s *service) Status(ctx context.Context, _ *emptypb.Empty) (*rpc.DaemonStatus, error) {\n\ts.sessionLock.RLock()\n\tdefer s.sessionLock.RUnlock()\n\tr := &rpc.DaemonStatus{\n\t\tManaged: s.managed,\n\t\tVersion: client.VersionInfo(ctx),\n\t}\n\tif s.session != nil {\n\t\tr.OutboundConfig = s.session.getNetworkConfig()\n\t}\n\treturn r, nil\n}\n\nfunc (s *service) Quit(ctx context.Context, _ *emptypb.Empty) (*rpc.QuitResponse, error) {\n\ts.cancelSession(ctx)\n\ts.quit()\n\treturn &rpc.QuitResponse{RootDaemonWillContinue: s.managed}, nil\n}\n\nfunc (s *service) SetDNSTopLevelDomains(ctx context.Context, domains *rpc.Domains) (*emptypb.Empty, error) {\n\terr := s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\tsession.SetTopLevelDomains(domains.Domains)\n\t\treturn nil\n\t})\n\treturn &emptypb.Empty{}, err\n}\n\nfunc (s *service) SetDNSExcludes(ctx context.Context, req *rpc.SetDNSExcludesRequest) (*emptypb.Empty, error) {\n\terr := s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\tsession.SetExcludes(req.Excludes)\n\t\treturn nil\n\t})\n\treturn &emptypb.Empty{}, err\n}\n\nfunc (s *service) SetDNSMappings(ctx context.Context, req *rpc.SetDNSMappingsRequest) (*emptypb.Empty, error) {\n\terr := s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\tsession.SetMappings(req.Mappings)\n\t\treturn nil\n\t})\n\treturn &emptypb.Empty{}, err\n}\n\nfunc (s *service) Connect(ctx context.Context, info *rpc.NetworkConfig) (reply *rpc.DaemonStatus, err error) {\n\treply = &rpc.DaemonStatus{Version: client.VersionInfo(ctx)}\n\terr = s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\treply.OutboundConfig = s.session.getNetworkConfig()\n\t\treturn nil\n\t})\n\tif err == nil {\n\t\treturn reply, nil\n\t}\n\n\ts.sessionLock.Lock()\n\tdefer s.sessionLock.Unlock()\n\tif s.session != nil {\n\t\t// Someone took the lock before we did and created a session.k\n\t\treply.OutboundConfig = s.session.getNetworkConfig()\n\t\treturn reply, nil\n\t}\n\n\tcfg, err := client.UnmarshalJSONConfig(info.ClientConfig, false)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsessionCtx, sessionCancel := context.WithCancel(s)\n\tvar sn *session\n\tsn, err = createSession(client.WithConfig(sessionCtx, cfg), ctx, info, s.activity)\n\tif err != nil {\n\t\tsessionCancel()\n\t\treturn nil, err\n\t}\n\tif !s.managed {\n\t\t// Only reload log level from client config if not running as a managed service.\n\t\t// A managed service should maintain its own log level configuration.\n\t\tclient.ReloadLogLevel(sn)\n\t}\n\treply.OutboundConfig = sn.getNetworkConfig()\n\tinitErrCh := make(chan error, 1)\n\n\tsessionRunning := make(chan struct{})\n\tgo func() {\n\t\tdefer func() {\n\t\t\tsessionCancel()\n\t\t\tif !s.managed {\n\t\t\t\t// Restore log level from service config after session ends.\n\t\t\t\tclient.ReloadLogLevel(s)\n\t\t\t}\n\t\t\tclose(sessionRunning)\n\t\t}()\n\t\tsn.run(initErrCh)\n\t\ts.clearSession(sn)\n\t}()\n\tselect {\n\tcase <-sn.Done():\n\t\t// Session (or service) was canceled.\n\t\treturn nil, status.Error(codes.Canceled, \"session canceled\")\n\tcase <-ctx.Done():\n\t\t// gRPC context was canceled, probably by the caller.\n\t\tsessionCancel()\n\t\treturn nil, status.Error(codes.Canceled, \"connect call canceled\")\n\tcase err = <-initErrCh:\n\t\tif err != nil {\n\t\t\t// Session failed to initialize.\n\t\t\tsessionCancel()\n\t\t\treturn nil, err\n\t\t}\n\t\t// Session initialized successfully.\n\t}\n\ts.session = sn\n\ts.sessionCancel = sessionCancel\n\ts.sessionRunning = sessionRunning\n\treturn reply, nil\n}\n\nfunc (s *service) Disconnect(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {\n\ts.cancelSession(ctx)\n\treturn &emptypb.Empty{}, nil\n}\n\nfunc (s *service) clearSession(oldSession *session) {\n\ts.sessionLock.Lock()\n\tif s.session == oldSession {\n\t\ts.session = nil\n\t\ts.sessionCancel = nil\n\t}\n\ts.sessionLock.Unlock()\n}\n\nfunc (s *service) cancelSession(ctx context.Context) {\n\t// We must use a shared read lock when cancelling to avoid a deadlock.\n\tvar oldSession *session\n\terr := s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\ts.sessionCancel()\n\t\toldSession = session\n\t\treturn nil\n\t})\n\tif err == nil {\n\t\t// Session is officially dead, and we don't want anyone to use during the time when it's shutting down.\n\t\ts.clearSession(oldSession)\n\t}\n}\n\nfunc (s *service) TranslateEnvIPs(ctx context.Context, environment *rpc.Environment) (result *rpc.Environment, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\tresult = session.translateEnvIPs(environment)\n\t\treturn nil\n\t})\n\treturn result, err\n}\n\nfunc (s *service) WaitForNetwork(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {\n\terr := s.withSession(ctx, func(ctx context.Context, session *session) error {\n\t\tif err, ok := <-session.networkReady(ctx); ok {\n\t\t\treturn status.Error(codes.Unavailable, err.Error())\n\t\t}\n\t\treturn nil\n\t})\n\treturn &emptypb.Empty{}, err\n}\n\nfunc (s *service) GetNetworkConfig(ctx context.Context, _ *emptypb.Empty) (nc *rpc.NetworkConfig, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\tnc = session.getNetworkConfig()\n\t\treturn nil\n\t})\n\treturn nc, err\n}\n\nfunc (s *service) WaitForAgentIP(ctx context.Context, request *rpc.WaitForAgentIPRequest) (rsp *rpc.WaitForAgentIPResponse, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session *session) error {\n\t\trsp, err = session.waitForAgentIP(ctx, request)\n\t\treturn err\n\t})\n\treturn rsp, err\n}\n\nfunc (s *service) SetLogLevel(ctx context.Context, request *manager.LogLevelRequest) (*emptypb.Empty, error) {\n\tlvl, err := clog.ParseLevel(request.LogLevel)\n\tif err != nil {\n\t\treturn &emptypb.Empty{}, status.Error(codes.InvalidArgument, err.Error())\n\t}\n\tduration := time.Duration(0)\n\tif request.Duration != nil {\n\t\tduration = request.Duration.AsDuration()\n\t}\n\treturn &emptypb.Empty{}, logging.SetAndStoreTimedLevel(ctx, s.timedLogLevel, lvl, duration, client.RootDaemonName)\n}\n\nfunc (s *service) LookupIP(ctx context.Context, request *rpc.LookupIPRequest) (rsp *rpc.LookupIPResponse, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\trsp, err = session.lookupIP(request)\n\t\treturn err\n\t})\n\treturn rsp, err\n}\n\nfunc (s *service) ResolvePort(ctx context.Context, request *rpc.ResolvePortRequest) (rsp *rpc.ResolvePortResponse, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session *session) error {\n\t\tap, err := session.resolvePort(ctx, request.Host, request.Port)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tapb, err := ap.MarshalBinary()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trsp = &rpc.ResolvePortResponse{HostPort: apb}\n\t\treturn nil\n\t})\n\treturn rsp, err\n}\n\nfunc (s *service) RerouteRemotePort(ctx context.Context, request *rpc.ReroutePortRequest) (rsp *emptypb.Empty, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session *session) error {\n\t\tvar ap types.AddrPortProto\n\t\terr = ap.UnmarshalBinary(request.DstHostPort)\n\t\tif err == nil {\n\t\t\tsession.rerouteRemotePort(ap, uint16(request.SrcPort))\n\t\t}\n\t\treturn err\n\t})\n\treturn &emptypb.Empty{}, err\n}\n\nfunc (s *service) withSession(ctx context.Context, f func(context.Context, *session) error) (err error) {\n\ts.sessionLock.RLock()\n\tdefer s.sessionLock.RUnlock()\n\tif s.session == nil {\n\t\treturn status.Error(codes.Unavailable, \"no active session\")\n\t}\n\tselect {\n\tcase <-s.session.Done():\n\t\treturn status.Error(codes.Canceled, \"session canceled\")\n\tdefault:\n\t\treturn f(server.NewCombinedContext(s.session, ctx), s.session)\n\t}\n}\n"
  },
  {
    "path": "pkg/client/rootd/in_process.go",
    "content": "package rootd\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// InProcSession is like a session but also implements the daemon.DaemonClient interface. This makes it possible to use the session\n// in-process from the user daemon without starting the root daemon gRPC service.\ntype InProcSession struct {\n\t*session\n}\n\nfunc (rd *InProcSession) Version(ctx context.Context, _ *empty.Empty, _ ...grpc.CallOption) (*common.VersionInfo, error) {\n\treturn client.VersionInfo(ctx), nil\n}\n\nfunc (rd *InProcSession) Status(ctx context.Context, _ *empty.Empty, _ ...grpc.CallOption) (*rpc.DaemonStatus, error) {\n\treturn &rpc.DaemonStatus{\n\t\tVersion:        client.VersionInfo(ctx),\n\t\tOutboundConfig: rd.getNetworkConfig(),\n\t}, nil\n}\n\nfunc (rd *InProcSession) Quit(context.Context, *empty.Empty, ...grpc.CallOption) (*rpc.QuitResponse, error) {\n\treturn &rpc.QuitResponse{}, nil\n}\n\nfunc (rd *InProcSession) Connect(ctx context.Context, _ *rpc.NetworkConfig, opts ...grpc.CallOption) (*rpc.DaemonStatus, error) {\n\treturn rd.Status(ctx, nil, opts...)\n}\n\nfunc (rd *InProcSession) Disconnect(context.Context, *empty.Empty, ...grpc.CallOption) (*empty.Empty, error) {\n\treturn &empty.Empty{}, nil\n}\n\nfunc (rd *InProcSession) GetNetworkConfig(context.Context, *empty.Empty, ...grpc.CallOption) (*rpc.NetworkConfig, error) {\n\treturn rd.getNetworkConfig(), nil\n}\n\nfunc (rd *InProcSession) SetDNSTopLevelDomains(_ context.Context, in *rpc.Domains, _ ...grpc.CallOption) (*empty.Empty, error) {\n\trd.SetTopLevelDomains(in.Domains)\n\treturn &empty.Empty{}, nil\n}\n\nfunc (rd *InProcSession) SetDNSExcludes(_ context.Context, in *rpc.SetDNSExcludesRequest, _ ...grpc.CallOption) (*empty.Empty, error) {\n\trd.SetExcludes(in.Excludes)\n\treturn &empty.Empty{}, nil\n}\n\nfunc (rd *InProcSession) SetDNSMappings(_ context.Context, in *rpc.SetDNSMappingsRequest, _ ...grpc.CallOption) (*empty.Empty, error) {\n\trd.SetMappings(in.Mappings)\n\treturn &empty.Empty{}, nil\n}\n\nfunc (rd *InProcSession) SetLogLevel(context.Context, *manager.LogLevelRequest, ...grpc.CallOption) (*empty.Empty, error) {\n\t// No loglevel when session runs in the same process as the user daemon.\n\treturn &empty.Empty{}, nil\n}\n\nfunc (rd *InProcSession) TranslateEnvIPs(_ context.Context, in *rpc.Environment, _ ...grpc.CallOption) (*rpc.Environment, error) {\n\tin = rd.translateEnvIPs(in)\n\treturn in, nil\n}\n\nfunc (rd *InProcSession) WaitForNetwork(ctx context.Context, _ *empty.Empty, _ ...grpc.CallOption) (*empty.Empty, error) {\n\tif err, ok := <-rd.networkReady(ctx); ok {\n\t\treturn &empty.Empty{}, status.Error(codes.Unavailable, err.Error())\n\t}\n\treturn &empty.Empty{}, nil\n}\n\nfunc (rd *InProcSession) LookupIP(_ context.Context, request *rpc.LookupIPRequest, _ ...grpc.CallOption) (*rpc.LookupIPResponse, error) {\n\treturn rd.lookupIP(request)\n}\n\nfunc (rd *InProcSession) ResolvePort(ctx context.Context, request *rpc.ResolvePortRequest, _ ...grpc.CallOption) (*rpc.ResolvePortResponse, error) {\n\tap, err := rd.resolvePort(ctx, request.Host, request.Port)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tapb, err := ap.MarshalBinary()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &rpc.ResolvePortResponse{HostPort: apb}, nil\n}\n\nfunc (rd *InProcSession) RerouteRemotePort(_ context.Context, request *rpc.ReroutePortRequest, _ ...grpc.CallOption) (*empty.Empty, error) {\n\tvar ap types.AddrPortProto\n\tif err := ap.UnmarshalBinary(request.DstHostPort); err != nil {\n\t\treturn nil, err\n\t}\n\trd.rerouteRemotePort(ap, uint16(request.SrcPort))\n\treturn &empty.Empty{}, nil\n}\n\nfunc (rd *InProcSession) WaitForAgentIP(ctx context.Context, request *rpc.WaitForAgentIPRequest, _ ...grpc.CallOption) (*rpc.WaitForAgentIPResponse, error) {\n\treturn rd.waitForAgentIP(ctx, request)\n}\n\nfunc (rd *InProcSession) ActivityWatcher(ctx context.Context, in *empty.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[rpc.Activity], error) {\n\t// The InProcSession shortcuts this watcher with a channel passed to the [NewInProcSession] constructor.\n\treturn nil, status.Error(codes.Unimplemented, \"ActivityWatcher not implemented\")\n}\n\n// NewInProcSession returns a root daemon session suitable to use in-process (from the user daemon) and is primarily intended for\n// when the user daemon runs in a docker container with NET_ADMIN capabilities.\nfunc NewInProcSession(\n\tkc *k8s.Cluster,\n\tmi *rpc.NetworkConfig,\n\tmc *grpc.ClientConn,\n\tver semver.Version,\n\tactivity chan<- time.Time,\n\tisPodDaemon bool,\n) (*InProcSession, error) {\n\tsession, err := newSession(kc, mi, mc, ver, activity, isPodDaemon)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &InProcSession{session: session}, nil\n}\n"
  },
  {
    "path": "pkg/client/rootd/service.go",
    "content": "package rootd\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/server\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/pprof\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/sigctx\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\nconst (\n\ttitleName    = \"Root Daemon\"\n\tpprofFlag    = \"pprof\"\n\tcacheDirFlag = \"cache\"\n\tconfigFlag   = \"config\"\n\tlogfileFlag  = \"logfile\"\n\taddressFlag  = \"address\"\n\tmanagedFlag  = \"managed\"\n)\n\n// service represents the state of the Telepresence Daemon.\ntype service struct {\n\tcontext.Context\n\trpc.UnsafeDaemonServer\n\tquit          context.CancelFunc\n\ttimedLogLevel log.TimedLevel\n\n\t// sessionLock protects the session, sessionCancel, and sessionRunning fields.\n\tsessionLock   sync.RWMutex\n\tsession       *session\n\tsessionCancel context.CancelFunc\n\n\t// sessionRunning is closed when the session is done running.\n\tsessionRunning chan struct{}\n\tactivity       chan time.Time\n\tmanaged        bool\n}\n\nfunc newService(cfg client.Config, managed bool) *service {\n\ts := &service{\n\t\ttimedLogLevel:  log.NewTimedLevel(cfg.LogLevels().RootDaemon, clog.SetTreeLevel),\n\t\tsessionRunning: make(chan struct{}),\n\t\tactivity:       make(chan time.Time, 10),\n\t\tmanaged:        managed,\n\t}\n\tclose(s.sessionRunning)\n\treturn s\n}\n\n// Command returns the telepresence sub-command \"rootd\".\nfunc Command(ctx context.Context) *cobra.Command {\n\tcmd := &cobra.Command{\n\t\tUse:    client.RootDaemonName,\n\t\tShort:  \"Launch Telepresence \" + titleName,\n\t\tArgs:   cobra.NoArgs,\n\t\tHidden: true,\n\t\tLong:   `The Telepresence ` + titleName + ` is a long-lived background component that manages connections and network state.`,\n\t\tRunE:   run,\n\t}\n\tflags := cmd.Flags()\n\tflags.Uint16(pprofFlag, 0, \"start pprof server on the given port\")\n\tflags.String(logfileFlag, \"\", `Log file to write to { <path to a file> | \"stdout\" | \"stderr\" | \"std\" | \"managed\" }\n\"std\" will cause the daemon to log informal messages to stdout and error messages to stderr\n\"managed\" is like \"std\", but without timestamps and level tags for \"error\" or \"info\" messages`)\n\tflags.String(cacheDirFlag, \"\", `Path to the Telepresence cache directory`)\n\tflags.String(configFlag, \"\", `Path to the Telepresence configuration file`)\n\tflags.String(addressFlag, \"\", \"TCP address to listen to\")\n\tflags.Bool(managedFlag, false, \"The daemon is managed by the system and will disconnect, but not exit, when it receives RPC calls to Quit\")\n\treturn cmd\n}\n\nfunc (s *service) configReload(c context.Context) error {\n\treturn client.WatchConfig(c, func(c context.Context) error {\n\t\tclient.ReloadLogLevel(c)\n\t\treturn nil\n\t})\n}\n\nfunc (s *service) serveGrpc(c context.Context, groupCancel context.CancelFunc, l net.Listener) error {\n\tvar opts []grpc.ServerOption\n\tcfg := client.GetConfig(c)\n\tif mz := cfg.Grpc().MaxReceiveSize(); mz > 0 {\n\t\topts = append(opts, grpc.MaxRecvMsgSize(int(mz)))\n\t}\n\n\tif s.managed {\n\t\t// This essentially makes the quit() function wait until the session is done but otherwise do nothing.\n\t\tgroupCancel = func() {}\n\t}\n\ts.Context = c\n\ts.quit = func() {\n\t\tgroupCancel()\n\t\ts.sessionLock.RLock()\n\t\tsessionRunning := s.sessionRunning\n\t\ts.sessionLock.RUnlock()\n\t\t<-sessionRunning\n\t}\n\tsvc := server.New(s, opts...)\n\trpc.RegisterDaemonServer(svc, s)\n\treturn server.Serve(s, svc, l)\n}\n\n// run is the main function when executing as the daemon.\nfunc run(cmd *cobra.Command, args []string) error {\n\tif !proc.IsAdmin() {\n\t\tmsg := fmt.Sprintf(\"telepresence %s must run with elevated privileges\", client.RootDaemonName)\n\t\tioutil.Println(os.Stderr, msg)\n\t\treturn errors.New(msg)\n\t}\n\treturn sigctx.DoWithSignalHandler(cmd.Context(), func(ctx context.Context) error {\n\t\treturn internalRun(ctx, cmd.Flags())\n\t})\n}\n\nfunc internalRun(c context.Context, flags *pflag.FlagSet) error {\n\tcacheDir := flags.Lookup(cacheDirFlag).Value.String()\n\tif cacheDir != \"\" {\n\t\tc = filelocation.WithAppUserCacheDir(c, cacheDir)\n\t}\n\tvar cfg client.Config\n\tvar err error\n\n\tvar configFile string\n\tcfgFlag := flags.Lookup(configFlag)\n\tif cfgFlag.Changed {\n\t\tconfigFile = cfgFlag.Value.String()\n\t}\n\n\tif configFile == \"\" {\n\t\tcfg = client.GetDefaultConfig()\n\t} else {\n\t\tc = client.WithConfigFile(c, configFile)\n\t\tc = filelocation.WithAppUserConfigDir(c, filepath.Dir(configFile))\n\t\tcfg, err = client.LoadConfig(c)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to load config: %w\", err)\n\t\t}\n\t}\n\tc = client.WithConfig(c, cfg)\n\n\taddrFlag := flags.Lookup(addressFlag)\n\tif !addrFlag.Changed {\n\t\treturn fmt.Errorf(\"must specify %s\", addressFlag)\n\t}\n\taddrStr := addrFlag.Value.String()\n\tnqFlag := flags.Lookup(managedFlag)\n\n\tvar managed bool\n\tif nqFlag.Changed {\n\t\tmanaged, _ = strconv.ParseBool(nqFlag.Value.String())\n\t}\n\n\tif pprofPort, _ := flags.GetUint16(pprofFlag); pprofPort > 0 {\n\t\tgo func() {\n\t\t\tif err := pprof.PprofServer(c, pprofPort); err != nil {\n\t\t\t\tclog.Error(c, err)\n\t\t\t}\n\t\t}()\n\t}\n\tlogFile := flags.Lookup(logfileFlag).Value.String()\n\tc, err = logging.InitContext(c, logFile, cfg.LogLevels().RootDaemon, logging.RotateDaily, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tc = clog.WithGroup(c, client.RootDaemonName)\n\n\tclog.Debug(c, shellquote.ShellString(os.Args[0], os.Args[1:]))\n\n\tclog.Info(c, \"---\")\n\tclog.Infof(c, \"Telepresence %s %s starting...\", titleName, client.DisplayVersion())\n\tclog.Infof(c, \"PID is %d\", os.Getpid())\n\tclog.Info(c, \"\")\n\n\t// Listen on domain unix domain socket. The listener must be opened before other tasks because\n\t// the CLI client will only wait for a short period of time for the socket to appear before it\n\t// gives up.\n\tlc := net.ListenConfig{}\n\tgrpcListener, err := lc.Listen(c, \"tcp\", addrStr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer grpcListener.Close()\n\n\tdaemonAddress := grpcListener.Addr().(interface{ AddrPort() netip.AddrPort }).AddrPort()\n\tclog.Debugf(c, \"Listener opened on %s\", grpcListener.Addr())\n\n\td := newService(cfg, managed)\n\tif err = logging.LoadTimedLevelFromCache(c, d.timedLogLevel, client.RootDaemonName); err != nil {\n\t\tclog.Error(c, err)\n\t\treturn err\n\t}\n\tvif.InitLogger(c)\n\n\tc, cancel := context.WithCancel(c)\n\tg := log.NewGroup(c)\n\trunAliveAndCancellation(g, daemonAddress.Port(), cancel, managed)\n\n\t// Add a reload function that triggers on create and write of the config.yml file.\n\tg.Go(\"config-reload\", d.configReload)\n\tg.Go(\"server-grpc\", func(c context.Context) error { return d.serveGrpc(c, cancel, grpcListener) })\n\terr = g.Wait()\n\tif err != nil {\n\t\tclog.Error(c, err)\n\t}\n\treturn err\n}\n\nfunc runAliveAndCancellation(g log.Group, daemonPort uint16, cancel context.CancelFunc, managed bool) {\n\tg.Go(\"info-kicker\", func(ctx context.Context) error {\n\t\t// Ensure that the daemon info file is kept recent. This tells clients that we're alive.\n\t\til := daemon.NewRootInfoLoader(ctx, managed)\n\t\tif managed {\n\t\t\tinfo := &daemon.RootInfo{DaemonPort: daemonPort}\n\t\t\terr := il.SaveInfo(info, daemon.InfoFileName)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn il.KeepInfoAlive(daemon.InfoFileName)\n\t})\n\tg.Go(\"info-watcher\", func(ctx context.Context) error {\n\t\t// Cancel the session if the daemon info file is removed.\n\t\til := daemon.NewRootInfoLoader(ctx, managed)\n\t\treturn il.WatchInfos(func(ctx context.Context) error {\n\t\t\tok, err := il.InfoExists(daemon.InfoFileName)\n\t\t\tif err == nil && !ok {\n\t\t\t\tclog.Debugf(ctx, \"info-watcher cancels everything because daemon info %s does not exist\", daemon.InfoFileName)\n\t\t\t\tcancel()\n\t\t\t}\n\t\t\treturn err\n\t\t}, daemon.InfoFileName)\n\t})\n}\n"
  },
  {
    "path": "pkg/client/rootd/session.go",
    "content": "package rootd\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\tdns2 \"github.com/miekg/dns\"\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/connectivity\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\t\"google.golang.org/grpc/status\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\t\"google.golang.org/protobuf/types/known/timestamppb\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/agentpf\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/bwcompat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker/teleroute\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/portforward\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/rootd/dns\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/rootd/vip\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dnsproxy\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\tgrpcErrors \"github.com/telepresenceio/telepresence/v2/pkg/grpc/errors\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/watcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/slice\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/subnet\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/vif\"\n)\n\ntype agentSubnet struct {\n\tnetip.Prefix\n\tworkload string\n}\n\ntype agentVIP struct {\n\tworkload      string\n\tdestinationIP netip.Addr\n}\n\n// session resolves DNS names and routes outbound traffic that is centered around a TUN device. The router is\n// similar to a TUN-to-SOCKS5 but uses a bidirectional gRPC muxTunnel instead of SOCKS when communicating with the\n// traffic-manager. The addresses of the device are derived from IP addresses sent to it from the user\n// daemon (which in turn receives them from the cluster).\n//\n// Data sent to the device is received as L3 IP-packets and parsed into L4 UDP and TCP before they\n// are dispatched over the muxTunnel. Returned payloads are wrapped as IP-packets before written\n// back to the device. This L3 <=> L4 conversation is made using gvisor.dev/gvisor/pkg/tcpip.\n//\n// Connection pooling:\n//\n// For UDP and TCP packets, a ConnID is created which uniquely identifies a combination of protocol,\n// source IP, source port, destination IP, and destination port. A handler is then obtained that matches\n// that ID (active handlers are cached in a tunnel.Pool) and the packet is then sent to that handler.\n// The handler typically sends the ConnID and the payload of the packet over to the traffic-manager\n// using the gRPC ClientTunnel. At the receiving en din the traffic-manager, a similar tunnel.Pool obtains\n// a corresponding handler which manages a net.Conn matching the ConnID in the cluster.\n//\n// A zero session is invalid; you must use the createSession or the newSession function.\ntype session struct {\n\t*k8s.Cluster\n\n\ttunVif *vif.TunnelingDevice\n\n\tteleroute teleroute.Server\n\n\t// managerConn is the connection to the traffic-manager.\n\tmanagerConn *grpc.ClientConn\n\n\t// agentClients provides the gRPC tunnel to traffic-agents in the connected namespace\n\tagentClients agentpf.Clients\n\n\t// managerVersion is the version of the connected traffic-manager\n\tmanagerVersion semver.Version\n\n\t// connPool contains handlers that represent active connections. Those handlers\n\t// are obtained using a connpool.ConnID.\n\thandlers *tunnel.Pool\n\n\t// The local dns server\n\tdnsServer *dns.Server\n\n\t// vifDNS is the address and port of the DNS server attached to the TUN device. This is currently only\n\t// used in conjunction with systemd-resolved. The current macOS and the overriding solution\n\t// will dispatch directly to the local DNS Service without going through the TUN device, but\n\t// that may change later if we decide to dispatch to the DNS-server in the cluster.\n\tvifDNS netip.AddrPort\n\n\t// localDNS is the address and port of the local DNS Service.\n\tlocalDNS netip.AddrPort\n\n\t// serviceSubnets reported by the traffic-manager\n\tserviceSubnets []netip.Prefix\n\n\t// podSubnets reported by the traffic-manager\n\tpodSubnets []netip.Prefix\n\n\t// Subnets configured by the user\n\talsoProxySubnets []netip.Prefix\n\n\t// Subnets configured by the user to never be proxied\n\tneverProxySubnets []netip.Prefix\n\n\t// Like neverProxySubnets but stripped from the ones that aren't proxied anyway\n\teffectiveNeverProxy []netip.Prefix\n\n\t// Subnets that will be mapped even if they conflict with local routes\n\tallowConflictingSubnets []netip.Prefix\n\n\t// localTranslationTable maps an IP returned by the cluster's DNS to a virtual IP created by this server.\n\tlocalTranslationTable *xsync.Map[netip.Addr, netip.Addr]\n\n\t// IP addresses that the cluster's DNS resolves that are contained in one of the subnets in this\n\t// slice are translated to a virtual IP (cached in the localTranslationTable)\n\tlocalTranslationSubnets []agentSubnet\n\n\t// virtualIPs maps a virtual IP to an agent tunnel.\n\tvirtualIPs *xsync.Map[netip.Addr, agentVIP]\n\n\t// vipGenerator generates virtual IPs for a given range.\n\tvipGenerator vip.Generator\n\n\t// closing is set during shutdown and can have the values:\n\t//   0 = running\n\t//   1 = closing\n\t//   2 = closed\n\tclosing int32\n\n\t// session contains the manager session\n\tsession *manager.SessionInfo\n\n\t// rndSource is the source for the random number generator in the TCP handlers\n\trndSource rand.Source\n\n\t// Telemetry counters for DNS lookups\n\tdnsLookups  int\n\tdnsFailures int\n\n\t// Whether pods should be proxied by the TUN-device\n\tproxyClusterPods bool\n\n\t// Whether services should be proxied by the TUN-device\n\tproxyClusterSvcs bool\n\n\t// dnsServerSubnet is normally never set. It is only used when neither proxyClusterPods nor the\n\t// proxyClusterSvcs are set. In this situation, the VIF would be left without a primary subnet, so\n\t// it will instead route very small subnet with 30 bit mask, large enough to hold:\n\t//\n\t//   n.n.n.0 The IP identifying the subnet\n\t//   n.n.n.1 The IP of the (non existent) gateway\n\t//   n.n.n.2 The IP of the DNS server\n\t//   n.n.n.3 Unused\n\t//\n\t// The subnet is guaranteed to be free from all other routed subnets.\n\t//\n\t// NOTE: On macOS, where DNS is controlled by adding entries in /etc/resolver that points directly\n\t// to a port on localhost, there's no need for this subnet.\n\tdnsServerSubnet netip.Prefix\n\n\t// vifReady is closed when the virtual network interface has been configured.\n\tvifReady chan error\n\n\tsubnetViaWorkloads []*rpc.SubnetViaWorkload\n\n\t// daemon runs as part of a pod-daemon setup.\n\tpodDaemon bool\n\troutesCh  chan []netip.Prefix\n\n\t// Timestamps sent on this channel are propagated to the user daemon.\n\tactivity chan<- time.Time\n\n\t// Maps one UDP or TCP AddrPort to another\n\tl4PortMap *xsync.Map[types.AddrPortProto, uint16]\n\n\tlookupSequencer *xsync.Map[string, clusterLookupResult]\n}\n\n// createSession will establish a connection to the traffic-manager and return a new properly initialized session object.\nfunc createSession(sessionCtx, dialCtx context.Context, mi *rpc.NetworkConfig, activity chan<- time.Time) (s *session, err error) {\n\tclog.Info(sessionCtx, \"-- Starting new session\")\n\tkc, err := k8s.NewKubeconfig(sessionCtx, true, mi.KubeFlags, mi.ManagerNamespace, mi.KubeconfigData)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcl, err := k8s.NewCluster(kc, mi.MappedNamespaces)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tconn, _, ver, err := cl.ConnectToManager(dialCtx, mi.ManagerNamespace)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn newSession(cl, mi, conn, ver, activity, false)\n}\n\nfunc nope() bool { return false }\n\nfunc newSession(\n\tcluster *k8s.Cluster,\n\tmi *rpc.NetworkConfig,\n\tmanagerConn *grpc.ClientConn,\n\tver semver.Version,\n\tactivity chan<- time.Time,\n\tisPodDaemon bool,\n) (*session, error) {\n\tclog.Debugf(cluster, \"Creating session with id %v\", mi.Session)\n\n\ts := &session{\n\t\tCluster:               cluster,\n\t\thandlers:              tunnel.NewPool(),\n\t\trndSource:             rand.NewSource(time.Now().UnixNano()),\n\t\tsession:               mi.Session,\n\t\tmanagerConn:           managerConn,\n\t\tmanagerVersion:        ver,\n\t\tsubnetViaWorkloads:    mi.SubnetViaWorkloads,\n\t\tproxyClusterPods:      true,\n\t\tproxyClusterSvcs:      true,\n\t\tvifReady:              make(chan error, 2),\n\t\troutesCh:              make(chan []netip.Prefix, 2),\n\t\tactivity:              activity,\n\t\tpodDaemon:             isPodDaemon,\n\t\tlocalTranslationTable: xsync.NewMap[netip.Addr, netip.Addr](),\n\t\tvirtualIPs:            xsync.NewMap[netip.Addr, agentVIP](),\n\t\tl4PortMap:             xsync.NewMap[types.AddrPortProto, uint16](),\n\t}\n\tcfg := client.GetConfig(s)\n\n\t// Use simple lookups unless the traffic-manager version is less than 2.25.0 (not supported), or if the user has explicitly\n\t// requested complex lookups. The presence of the s.lookupSequencer will trigger simple lookups.\n\tif !(cfg.DNS().UseComplexLookup || semver.MustParse(ver.FinalizeVersion()).LT(semver.MustParse(\"2.25.0\"))) {\n\t\ts.lookupSequencer = xsync.NewMap[string, clusterLookupResult]()\n\t\tgo s.lookupSequencerGC()\n\t}\n\n\trt := cfg.Routing()\n\tvar err error\n\ts.alsoProxySubnets, err = validateSubnets(\"also-proxy\", rt.AlsoProxy, s.alsoProxyVia)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclog.Infof(s, \"also-proxy subnets %v\", s.alsoProxySubnets)\n\n\ts.neverProxySubnets, err = validateSubnets(\"never-proxy\", rt.NeverProxy, nope)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclog.Infof(s, \"never-proxy subnets %v\", s.neverProxySubnets)\n\n\ts.allowConflictingSubnets, err = validateSubnets(\"allow-conflicting\", rt.AllowConflicting, nope)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclog.Infof(s, \"allow-conflicting subnets %v\", s.allowConflictingSubnets)\n\n\ts.dnsServer = dns.NewServer(cfg.DNS(), s.Namespace, s.clusterLookup)\n\ts.SetTopLevelDomains(nil)\n\n\t// Set ourselves as the default dialer for the session.\n\ts.Context = tunnel.WithDialer(s.Context, s)\n\n\t// Terminate the routes watcher\n\tgo func() {\n\t\t<-s.Done()\n\t\tclose(s.routesCh)\n\t}()\n\treturn s, nil\n}\n\n// lookupSequencerTTL is the maximum time to keep a lookup result cached with the purpose of avoiding\n// both A and AAAA lookups for the same name.\nconst lookupSequencerTTL = 500 * time.Millisecond\n\nfunc (s *session) managerClient() manager.ManagerClient {\n\treturn manager.NewManagerClient(s.managerConn)\n}\n\nfunc (s *session) lookupSequencerGC() {\n\t// Cleans the lookupSequencer from time to time to avoid that it grows too big if many different\n\t// names are looked up.\n\tmaps.GC(s.lookupSequencer, lookupSequencerTTL, s.Done(), func(key string, value clusterLookupResult) bool {\n\t\treturn time.Since(value.created) > lookupSequencerTTL\n\t})\n}\n\nfunc (s *session) resolvePort(ctx context.Context, host, portStr string) (ap types.AddrPortProto, err error) {\n\tix := strings.LastIndexByte(portStr, types.ProtoSeparator)\n\tproto := types.ProtoTCP\n\tif ix > 0 {\n\t\tproto, err = types.ParseProto(portStr[ix+1:])\n\t\tif err != nil {\n\t\t\treturn ap, err\n\t\t}\n\t\tportStr = portStr[:ix]\n\t}\n\n\tif port, err := types.ParsePort(portStr); err == nil {\n\t\tip, err := netip.ParseAddr(host)\n\t\tif err != nil {\n\t\t\tip, err = dns.LookupIP(ctx, s.localDNS, dns2.Fqdn(host))\n\t\t\tif err != nil {\n\t\t\t\treturn ap, err\n\t\t\t}\n\t\t}\n\t\treturn types.AddrPortProto{AddrPort: netip.AddrPortFrom(ip, port), Proto: proto}, nil\n\t}\n\n\t// The toPort is symbolic, so it must be resolved using the Kubernetes API.\n\t_, err = netip.ParseAddr(host)\n\tif err == nil {\n\t\treturn ap, errors.New(\"a symbolic port must be used with a service name, not an IP address\")\n\t}\n\treturn portforward.ResolveServiceAndPort(ctx, host, s.Namespace, portStr, proto)\n}\n\nfunc (s *session) rerouteRemotePort(ap types.AddrPortProto, newPort uint16) {\n\tif newPort != ap.Port() {\n\t\tclog.Debugf(s, \"Rerouting %s via %d\", ap, newPort)\n\n\t\t// Swap ports so that the port map reroutes requests for the new port to the original port.\n\t\ttoPort := ap.Port()\n\t\tap.AddrPort = netip.AddrPortFrom(ap.Addr(), newPort)\n\t\ts.l4PortMap.Store(ap, toPort)\n\t}\n}\n\ntype clusterLookupResult struct {\n\tcreated time.Time\n\trrs     dnsproxy.RRs\n\trCode   int\n\terr     error\n}\n\n// clusterLookup sends a Lookup or LookupDNS request to the traffic-manager and returns the result.\nfunc (s *session) clusterLookup(ctx context.Context, q *dns2.Question) (dnsproxy.RRs, int, error) {\n\tclog.Debugf(ctx, \"Lookup %s %q\", dns2.TypeToString[q.Qtype], q.Name)\n\ts.dnsLookups++\n\n\tif s.lookupSequencer == nil || !(q.Qtype == dns2.TypeA || q.Qtype == dns2.TypeAAAA) {\n\t\treturn s.complexClusterLookup(ctx, q)\n\t}\n\n\t// The lookupSequencer ensures that successive calls for the same name, whether they are A or AAAA, are not\n\t// performed concurrently. The traffic manager will return all known IPs for the name regardless of the type.\n\tresult, _ := s.lookupSequencer.Compute(q.Name, func(oldValue clusterLookupResult, loaded bool) (newValue clusterLookupResult, op xsync.ComputeOp) {\n\t\tif loaded && time.Since(oldValue.created) < lookupSequencerTTL {\n\t\t\treturn oldValue, xsync.CancelOp\n\t\t}\n\t\trrs, rCode, err := s.simpleLookup(ctx, q)\n\t\treturn clusterLookupResult{\n\t\t\tcreated: time.Now(),\n\t\t\trrs:     rrs,\n\t\t\trCode:   rCode,\n\t\t\terr:     err,\n\t\t}, xsync.UpdateOp\n\t})\n\treturn result.rrs, result.rCode, result.err\n}\n\nfunc (s *session) simpleLookup(ctx context.Context, question *dns2.Question) (dnsproxy.RRs, int, error) {\n\tvar lookupClient interface {\n\t\tLookup(context.Context, *manager.LookupRequest, ...grpc.CallOption) (*manager.LookupResponse, error)\n\t}\n\trequest := &manager.LookupRequest{Session: s.session, Name: question.Name}\n\tif ags := s.agentClients; ags != nil {\n\t\tlookupClient = ags.GetRandomAgent(ctx)\n\t}\n\tif lookupClient == nil {\n\t\tclog.Debugf(ctx, \"Using traffic-manager for lookup %q\", question.Name)\n\t\tlookupClient = s.managerClient()\n\t} else {\n\t\tclog.Debugf(ctx, \"Using traffic-agent for lookup %q\", question.Name)\n\t}\n\tresp, err := lookupClient.Lookup(ctx, request)\n\tif status.Code(err) == codes.Unimplemented {\n\t\treturn s.complexClusterLookup(ctx, question)\n\t}\n\tif err != nil {\n\t\ts.dnsFailures++\n\t\trCode := rcodeFromError(err)\n\t\tclog.Errorf(ctx, \"Lookup %q %s: %v\", question.Name, dns2.RcodeToString[rCode], err)\n\t\treturn nil, rCode, err\n\t}\n\tif len(resp.Ips) == 0 {\n\t\treturn nil, dns2.RcodeNameError, nil\n\t}\n\tips := make([]netip.Addr, len(resp.Ips))\n\tfor i := range resp.Ips {\n\t\t_ = ips[i].UnmarshalBinary(resp.Ips[i])\n\t}\n\tif len(s.localTranslationSubnets) > 0 {\n\t\tfor i, ip := range ips {\n\t\t\tips[i], err = s.GetLocalIP(ip)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, dns2.RcodeServerFailure, err\n\t\t\t}\n\t\t}\n\t}\n\tips4, ips6 := splitNameTypes(question.Name, ips)\n\trrs := ensureBothFamilies(question.Name, ips4, ips6)\n\trCode := dns2.RcodeSuccess\n\treturn rrs, rCode, err\n}\n\n// rrHeader creates a common DNS RR header for INET class.\nfunc rrHeader(name string, rrType uint16) dns2.RR_Header {\n\treturn dns2.RR_Header{\n\t\tName:   name,\n\t\tRrtype: rrType,\n\t\tClass:  dns2.ClassINET,\n\t}\n}\n\n// splitNameTypes converts the binary-encoded IPs into A and AAAA resource records.\nfunc splitNameTypes(name string, ips []netip.Addr) (dnsproxy.RRs, dnsproxy.RRs) {\n\tips4 := make(dnsproxy.RRs, 0)\n\tips6 := make(dnsproxy.RRs, 0)\n\n\tfor _, addr := range ips {\n\t\tif addr.Is6() {\n\t\t\tips6 = append(ips6, &dns2.AAAA{\n\t\t\t\tHdr:  rrHeader(name, dns2.TypeAAAA),\n\t\t\t\tAAAA: addr.AsSlice(),\n\t\t\t})\n\t\t} else {\n\t\t\tips4 = append(ips4, &dns2.A{\n\t\t\t\tHdr: rrHeader(name, dns2.TypeA),\n\t\t\t\tA:   addr.AsSlice(),\n\t\t\t})\n\t\t}\n\t}\n\treturn ips4, ips6\n}\n\n// ensureBothFamilies pads with an empty RR for the missing address family, preserving original behavior.\nfunc ensureBothFamilies(name string, ips4, ips6 dnsproxy.RRs) dnsproxy.RRs {\n\tswitch {\n\tcase len(ips4) > 0 && len(ips6) == 0:\n\t\tips6 = append(ips6, &dns2.AAAA{\n\t\t\tHdr: rrHeader(name, dns2.TypeAAAA),\n\t\t})\n\tcase len(ips6) > 0 && len(ips4) == 0:\n\t\tips4 = append(ips4, &dns2.A{\n\t\t\tHdr: rrHeader(name, dns2.TypeA),\n\t\t})\n\t}\n\treturn append(ips4, ips6...)\n}\n\n// clusterLookup sends a LookupDNS request to the traffic-manager and returns the result.\nfunc (s *session) complexClusterLookup(ctx context.Context, q *dns2.Question) (dnsproxy.RRs, int, error) {\n\tdnsResponse, err := s.managerClient().LookupDNS(ctx, &manager.DNSRequest{\n\t\tSession: s.session,\n\t\tName:    q.Name,\n\t\tType:    uint32(q.Qtype),\n\t})\n\tif err != nil {\n\t\ts.dnsFailures++\n\t\trCode := rcodeFromError(err)\n\t\tclog.Errorf(ctx, \"Lookup %s %q %s: %T %v\", dns2.TypeToString[q.Qtype], q.Name, dns2.RcodeToString[rCode], err, err)\n\t\treturn nil, rCode, err\n\t}\n\tanswer, rCode, err := dnsproxy.FromRPC(dnsResponse)\n\tif err != nil {\n\t\ts.dnsFailures++\n\t\treturn nil, dns2.RcodeServerFailure, err\n\t}\n\tif len(s.localTranslationSubnets) > 0 {\n\t\tfor _, rr := range answer {\n\t\t\tswitch rr := rr.(type) {\n\t\t\tcase *dns2.A:\n\t\t\t\tvar addr netip.Addr\n\t\t\t\taddr, err = s.GetLocalIP(netip.AddrFrom4([4]byte(rr.A)))\n\t\t\t\tif err == nil {\n\t\t\t\t\trr.A = addr.AsSlice()\n\t\t\t\t}\n\t\t\tcase *dns2.AAAA:\n\t\t\t\tvar addr netip.Addr\n\t\t\t\taddr, err = s.GetLocalIP(netip.AddrFrom16([16]byte(rr.AAAA)))\n\t\t\t\tif err == nil {\n\t\t\t\t\trr.AAAA = addr.AsSlice()\n\t\t\t\t}\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\trCode = dns2.RcodeServerFailure\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn answer, rCode, err\n}\n\n// rcodeFromError maps lookup errors to appropriate DNS RCODEs.\nfunc rcodeFromError(err error) int {\n\tswitch {\n\tcase errors.Is(err, context.DeadlineExceeded),\n\t\terrors.Is(err, context.Canceled),\n\t\tstatus.Code(err) == codes.DeadlineExceeded,\n\t\tstatus.Code(err) == codes.Canceled:\n\t\treturn dns2.RcodeNameError\n\tdefault:\n\t\treturn dns2.RcodeServerFailure\n\t}\n}\n\nfunc (s *session) GetLocalIP(destinationIP netip.Addr) (netip.Addr, error) {\n\tvar err error\n\tva, _ := s.localTranslationTable.LoadOrCompute(destinationIP, func() (netip.Addr, bool) {\n\t\tfor _, sn := range s.localTranslationSubnets {\n\t\t\tif sn.Contains(destinationIP) {\n\t\t\t\tvar nip netip.Addr\n\t\t\t\tnip, err = s.nextVirtualIP(sn.workload, destinationIP)\n\t\t\t\treturn nip, err != nil\n\t\t\t}\n\t\t}\n\t\treturn netip.Addr{}, true\n\t})\n\tif err == nil && va.IsValid() {\n\t\tdestinationIP = va\n\t}\n\treturn destinationIP, err\n}\n\nfunc (s *session) nextVirtualIP(workload string, destinationIP netip.Addr) (netip.Addr, error) {\n\tva, err := s.vipGenerator.Next()\n\tif err != nil {\n\t\treturn va, err\n\t}\n\ts.virtualIPs.Store(va, agentVIP{workload: workload, destinationIP: destinationIP})\n\treturn va, nil\n}\n\nfunc (s *session) getNetworkConfig() *rpc.NetworkConfig {\n\tmc := client.GetConfig(s)\n\tr := mc.Routing()\n\tif s.tunVif != nil {\n\t\tr.Subnets = s.tunVif.Router.GetRoutedSubnets()\n\t} else {\n\t\tr.Subnets = nil\n\t}\n\tif len(s.effectiveNeverProxy) > 0 {\n\t\tr.NeverProxy = make([]netip.Prefix, len(s.effectiveNeverProxy))\n\t\tcopy(r.NeverProxy, s.effectiveNeverProxy)\n\t} else {\n\t\tr.NeverProxy = nil\n\t}\n\tif len(s.alsoProxySubnets) > 0 {\n\t\tr.AlsoProxy = make([]netip.Prefix, len(s.alsoProxySubnets))\n\t\tcopy(r.AlsoProxy, s.alsoProxySubnets)\n\t} else {\n\t\tr.AlsoProxy = nil\n\t}\n\tif len(s.allowConflictingSubnets) > 0 {\n\t\tr.AllowConflicting = make([]netip.Prefix, len(s.allowConflictingSubnets))\n\t\tcopy(r.AllowConflicting, s.allowConflictingSubnets)\n\t} else {\n\t\tr.AllowConflicting = nil\n\t}\n\td := mc.DNS()\n\tif proc.RunningInContainer() && s.teleroute != nil {\n\t\tlas := s.teleroute.DaemonAddresses()\n\t\td.LocalAddresses = make([]netip.AddrPort, len(las))\n\t\tfor i, addr := range s.teleroute.DaemonAddresses() {\n\t\t\td.LocalAddresses[i] = netip.AddrPortFrom(addr, 53)\n\t\t}\n\t} else {\n\t\tif s.localDNS.IsValid() {\n\t\t\td.LocalAddresses = []netip.AddrPort{s.localDNS}\n\t\t} else {\n\t\t\td.LocalAddresses = nil\n\t\t}\n\t}\n\td.VIFAddress = s.vifDNS\n\n\tvar portMappings []string\n\tif psz := s.l4PortMap.Size(); psz > 0 {\n\t\tportMappings = make([]string, 0, psz)\n\t\ts.l4PortMap.Range(func(key types.AddrPortProto, origPort uint16) bool {\n\t\t\tportMappings = append(portMappings, fmt.Sprintf(\"%s:%d\", types.AddrPortProto{AddrPort: netip.AddrPortFrom(key.Addr(), origPort), Proto: key.Proto}, key.Port()))\n\t\t\treturn true\n\t\t})\n\t}\n\tjs, _ := json.Marshal(mc)\n\treturn &rpc.NetworkConfig{\n\t\tSession:          s.session,\n\t\tPortMappings:     portMappings,\n\t\tClientConfig:     js,\n\t\tMappedNamespaces: s.MappedNamespaces,\n\t\tManagerNamespace: k8s.GetManagerNamespace(s),\n\t}\n}\n\nfunc (s *session) configureDNS(vifDNS netip.AddrPort, localDNS netip.AddrPort) {\n\ts.vifDNS = vifDNS\n\ts.localDNS = localDNS\n}\n\n// shouldProxySubnet returns true unless the given subnet is covered by a subnet in the neverProxySubnets list.\nfunc (s *session) shouldProxySubnet(name string, sn netip.Prefix) bool {\n\tif sn.Addr().IsLoopback() {\n\t\tclog.Infof(s, \"Will not proxy %s subnet %s, because it is loopback\", name, sn)\n\t\treturn false\n\t}\n\tfor _, lt := range s.localTranslationSubnets {\n\t\tif subnet.Covers(lt.Prefix, sn) {\n\t\t\tclog.Infof(s, \"Will not proxy %s subnet %s, because it covered by --proxy-via %s=%s\", name, sn, lt.Prefix, lt.workload)\n\t\t\treturn false\n\t\t}\n\t}\n\tfor _, nps := range s.neverProxySubnets {\n\t\tif subnet.Covers(nps, sn) {\n\t\t\t// Allow if there's an also-proxy that is smaller, contradicting the never-proxy\n\t\t\tfor _, aps := range s.alsoProxySubnets {\n\t\t\t\tif subnet.Covers(nps, aps) && subnet.Covers(aps, sn) {\n\t\t\t\t\tclog.Infof(s, \"Will proxy %s subnet %s, because it is covered by also-proxy %s overriding never-proxy %s\", name, sn, nps, aps)\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t\tclog.Infof(s, \"Will not proxy %s subnet %s, because it is covered by never-proxy %s\", name, sn, nps)\n\t\t\treturn false\n\t\t}\n\t}\n\tfor _, npx := range s.subnetViaWorkloads {\n\t\tif name == \"service\" && npx.Subnet == \"service\" || name == \"pod\" && npx.Subnet == \"pods\" {\n\t\t\tclog.Infof(s, \"Will not proxy %s subnet %s, because it is covered by --proxy-via %s=%s\", name, sn, npx.Subnet, npx.Workload)\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// networkReady returns a channel that is close when both the VIF and DNS are ready.\nfunc (s *session) networkReady(ctx context.Context) <-chan error {\n\trdy := make(chan error, 2)\n\tgo func() {\n\t\tdefer close(rdy)\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\trdy <- ctx.Err()\n\t\tcase err, ok := <-s.vifReady:\n\t\t\tif ok {\n\t\t\t\trdy <- err\n\t\t\t} else {\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\tcase <-s.dnsServer.Ready():\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n\treturn rdy\n}\n\nfunc (s *session) watchClusterInfo(teleroutePort uint16) error {\n\treturn watcher.WatchWithRetry(s, \"WatchClusterInfo\", client.GetConfig(s).Grpc().WatchRetryInterval,\n\t\tfunc(ctx context.Context) (grpc.ServerStreamingClient[manager.ClusterInfo], error) {\n\t\t\treturn s.managerClient().WatchClusterInfo(ctx, s.session)\n\t\t},\n\t\tfunc(mgrInfo *manager.ClusterInfo) error {\n\t\t\tif err := s.readAdditionalRouting(mgrInfo); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-s.vifReady:\n\t\t\t\tif err := s.onClusterInfo(mgrInfo); err != nil {\n\t\t\t\t\tif !errors.Is(err, context.Canceled) {\n\t\t\t\t\t\tclog.Error(s, err)\n\t\t\t\t\t}\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tif err := s.onFirstClusterInfo(teleroutePort, mgrInfo); err != nil {\n\t\t\t\t\tif !errors.Is(err, context.Canceled) {\n\t\t\t\t\t\tclog.Error(s, err)\n\t\t\t\t\t}\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t\t// The user daemon will restore the session, and our managerClient will reconnect automatically\n\t\t// thanks to the built-in resilience in the port-forward logic, so there's no need for a repair\n\t\t// function here.\n\t\tnil,\n\t)\n}\n\n// createSubnetForDNSOnly will find a random IPv4 subnet that isn't currently routed and\n// attach the DNS server to that subnet.\nfunc (s *session) createSubnetForDNSOnly(mgrInfo *manager.ClusterInfo) {\n\t// Avoid alsoProxied and neverProxied\n\tavoid := make([]netip.Prefix, 0, len(s.alsoProxySubnets)+len(s.neverProxySubnets))\n\tavoid = append(avoid, s.alsoProxySubnets...)\n\tavoid = append(avoid, s.neverProxySubnets...)\n\n\t// Avoid the service subnets. They might be mapped with iptables (if running bare-metal) and\n\t// hence invisible when listing known routes.\n\tavoid = append(avoid, s.serviceSubnets...)\n\n\t// Avoid the pod subnets. They are probably visible as known routes, but we add them to\n\t// the avoid table to be sure.\n\tfor _, ps := range mgrInfo.PodSubnets {\n\t\tavoid = append(avoid, iputil.RPCToPrefix(ps))\n\t}\n\tvar err error\n\tif s.dnsServerSubnet, err = subnet.RandomIPv4Prefix(30, avoid); err != nil {\n\t\tclog.Error(s, err)\n\t}\n}\n\nfunc (s *session) onFirstClusterInfo(teleroutePort uint16, mgrInfo *manager.ClusterInfo) (err error) {\n\tdefer func() {\n\t\tif err != nil {\n\t\t\ts.vifReady <- err\n\t\t}\n\t\tclose(s.vifReady)\n\t}()\n\tif s.podDaemon {\n\t\treturn nil\n\t}\n\tif teleroutePort > 0 {\n\t\t// Always proxy pods and services when using the teleroute network, so that we can inject synthetic IP:s as needed. This means\n\t\t// never relying on Docker's default route to reach the cluster.\n\t\ts.proxyClusterPods = true\n\t\ts.proxyClusterSvcs = true\n\t} else {\n\t\ts.proxyClusterPods = !s.hasPodConnectivity(mgrInfo)\n\t\ts.proxyClusterSvcs = !s.hasSvcConnectivity(mgrInfo)\n\t}\n\treturn s.onClusterInfo(mgrInfo)\n}\n\nfunc (s *session) defaultRouteDNS(mgrInfo *manager.ClusterInfo, dnsAddr netip.Addr, subnets []netip.Prefix) (netip.Addr, []netip.Prefix, error) {\n\t// We'll need to synthesize a subnet where we can attach the DNS service when the VIF isn't configured\n\t// from cluster subnets. But not on darwin systems, because there the DNS is controlled by /etc/resolver\n\t// entries appointing the DNS service directly via localhost:<port>.\n\tif s.vipGenerator != nil {\n\t\tif !s.dnsServerSubnet.IsValid() {\n\t\t\ts.createSubnetForDNSOnly(mgrInfo)\n\t\t}\n\t\tclog.Infof(s, \"Adding service subnet %s (for DNS only)\", s.dnsServerSubnet)\n\t\tvar wl string\n\t\tfor _, snw := range s.subnetViaWorkloads {\n\t\t\tif sn, err := netip.ParsePrefix(snw.Subnet); err == nil && sn.Contains(dnsAddr) {\n\t\t\t\twl = snw.Workload\n\t\t\t\tif wl == \"local\" {\n\t\t\t\t\twl = \"\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.localTranslationSubnets = append(s.localTranslationSubnets, agentSubnet{\n\t\t\tPrefix:   s.dnsServerSubnet,\n\t\t\tworkload: wl,\n\t\t})\n\t\tvar err error\n\t\tdnsAddr, err = s.GetLocalIP(dnsAddr)\n\t\tif err != nil {\n\t\t\treturn dnsAddr, subnets, err\n\t\t}\n\t} else {\n\t\tif !s.dnsServerSubnet.IsValid() {\n\t\t\ts.createSubnetForDNSOnly(mgrInfo)\n\t\t}\n\t\tclog.Infof(s, \"Adding service subnet %s (for DNS only)\", s.dnsServerSubnet)\n\t\tsubnets = append(subnets, s.dnsServerSubnet)\n\t\tdnsIP := s.dnsServerSubnet.Addr().AsSlice()\n\t\tdnsIP[len(dnsIP)-1] = 2\n\t\tdnsAddr, _ = netip.AddrFromSlice(dnsIP)\n\t}\n\treturn dnsAddr, subnets, nil\n}\n\nfunc (s *session) onClusterInfo(mgrInfo *manager.ClusterInfo) (err error) {\n\tif s.podDaemon {\n\t\treturn nil\n\t}\n\tclog.Debugf(s, \"WatchClusterInfo update\")\n\tbwcompat.FixLegacyClusterInfo(mgrInfo)\n\n\tif mgrInfo.Routing == nil {\n\t\tmgrInfo.Routing = &manager.Routing{}\n\t}\n\n\ts.serviceSubnets = nil\n\ts.podSubnets = nil\n\n\tvar subnets []netip.Prefix\n\n\tif s.proxyClusterSvcs {\n\t\tfor _, sb := range mgrInfo.ServiceCidrs {\n\t\t\tvar cidr netip.Prefix\n\t\t\terr = cidr.UnmarshalBinary(sb)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif s.shouldProxySubnet(\"service\", cidr) {\n\t\t\t\tclog.Infof(s, \"Adding service subnet %s\", cidr)\n\t\t\t\tsubnets = append(subnets, cidr)\n\t\t\t}\n\t\t\ts.serviceSubnets = append(s.serviceSubnets, cidr)\n\t\t}\n\t}\n\n\tif s.proxyClusterPods {\n\t\tfor _, sn := range mgrInfo.PodSubnets {\n\t\t\tcidr := iputil.RPCToPrefix(sn)\n\t\t\tif s.shouldProxySubnet(\"pod\", cidr) {\n\t\t\t\tclog.Infof(s, \"Adding pod subnet %s\", cidr)\n\t\t\t\tsubnets = append(subnets, cidr)\n\t\t\t}\n\t\t\ts.podSubnets = append(s.podSubnets, cidr)\n\t\t}\n\t}\n\n\tif s.vipGenerator != nil {\n\t\tsubnets = append(subnets, s.vipGenerator.Subnet())\n\t\tclog.Debugf(s, \"Adding VIP subnet %q to TUN-device\", s.vipGenerator.Subnet().String())\n\t\ts.consolidateProxyViaWorkloads()\n\t}\n\n\tif !s.alsoProxyVia() {\n\t\tsubnets = append(subnets, s.alsoProxySubnets...)\n\t}\n\n\t// We use the ManagerPodIp as the dnsIP. The reason for this is that no one should ever\n\t// talk to the traffic-manager directly using the TUN device, so it's safe to use its\n\t// IP to impersonate the DNS server. All traffic sent to that IP, will be routed to\n\t// the local DNS server.\n\tvifDNS, ok := netip.AddrFromSlice(mgrInfo.ManagerPodIp)\n\tif !ok {\n\t\treturn fmt.Errorf(\"invalid traffic-manager pod ip address\")\n\t}\n\tif s.vipGenerator != nil {\n\t\tvifDNS, err = s.GetLocalIP(vifDNS)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\tdnsRouted := false\n\tif proc.RunningInContainer() {\n\t\tdnsRouted = true\n\t} else {\n\t\tfor _, sn := range subnets {\n\t\t\tif sn.Contains(vifDNS) {\n\t\t\t\tdnsRouted = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tif runtime.GOOS != \"darwin\" && !dnsRouted {\n\t\tvifDNS, subnets, err = s.defaultRouteDNS(mgrInfo, vifDNS, subnets)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdnsRouted = true\n\t}\n\n\tif dnsRouted {\n\t\td := mgrInfo.Dns\n\t\tdnsAddress := netip.AddrPortFrom(vifDNS, 53)\n\t\tclog.Infof(s, \"Setting client DNS to %s\", vifDNS)\n\t\tclog.Infof(s, \"Setting cluster domain to %q\", d.ClusterDomain)\n\t\ts.dnsServer.SetClusterDNS(d, dnsAddress)\n\t}\n\treturn s.reconcileSubnets(mgrInfo, subnets)\n}\n\nfunc (s *session) reconcileSubnets(mgrInfo *manager.ClusterInfo, subnets []netip.Prefix) error {\n\tif len(subnets) > 0 && s.tunVif == nil {\n\t\tvar err error\n\t\tif s.tunVif, err = vif.NewTunnelingDevice(s, s.streamCreator()); err != nil {\n\t\t\treturn fmt.Errorf(\"NewTunnelVIF: %w\", err)\n\t\t}\n\t}\n\n\tproxy, neverProxy, neverProxyOverrides := computeNeverProxyOverrides(s, subnets, s.neverProxySubnets)\n\ts.effectiveNeverProxy = neverProxy\n\tif s.tunVif == nil {\n\t\treturn nil\n\t}\n\trt := s.tunVif.Router\n\tclog.Debugf(s, \"allowConflicting is set to %v\", s.allowConflictingSubnets)\n\trt.UpdateWhitelist(s.allowConflictingSubnets)\n\n\terr := rt.ValidateRoutes(s, proxy)\n\tif err != nil {\n\t\tif s.vipGenerator != nil {\n\t\t\tclog.Debugf(s, \"vipGenerator is defined so error %s does not result in any translations\", err)\n\t\t\treturn err\n\t\t}\n\t\tif !client.GetConfig(s).Routing().AutoResolveConflicts {\n\t\t\tclog.Debugf(s, \"autoResolveConflicts is false so %s isn't resolving itself\", err)\n\t\t\treturn err\n\t\t}\n\t\t// Check each subnet and add a translation for those that conflict.\n\t\tfor _, pp := range proxy {\n\t\t\tif routeConflict := rt.ValidateRoutes(s, []netip.Prefix{pp}); routeConflict != nil {\n\t\t\t\tclog.Infof(s, \"Translating IPs in conflicting subnet %s to the virtual subnet\", pp)\n\t\t\t\ts.subnetViaWorkloads = append(s.subnetViaWorkloads, &rpc.SubnetViaWorkload{\n\t\t\t\t\tSubnet:   pp.String(),\n\t\t\t\t\tWorkload: \"local\",\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t\tif aErr := s.activateProxyViaWorkloads(); aErr != nil {\n\t\t\tclog.Errorf(s, \"activateProxyViaWorkloads: %v\", aErr)\n\t\t\treturn err\n\t\t}\n\t\treturn s.onClusterInfo(mgrInfo)\n\t}\n\n\tclog.Debugf(s, \"UpdatingRoutes %s, %s, %s\", proxy, s.effectiveNeverProxy, neverProxyOverrides)\n\terr = rt.UpdateRoutes(s, proxy, s.effectiveNeverProxy, neverProxyOverrides)\n\tif err != nil {\n\t\treturn err\n\t}\n\tsns := rt.GetRoutedSubnets()\n\tselect {\n\tcase <-s.Done():\n\tcase s.routesCh <- sns:\n\tdefault:\n\t}\n\treturn nil\n}\n\nfunc computeNeverProxyOverrides(ctx context.Context, subnets, nvp []netip.Prefix) (proxy, neverProxy, neverProxyOverrides []netip.Prefix) {\n\tneverProxy = slices.DeleteFunc(slices.Clone(nvp), func(nps netip.Prefix) bool {\n\t\tfor _, ds := range subnets {\n\t\t\tif ds.Overlaps(nps) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\t// This never-proxy is pointless because it's not a subnet that we are routing\n\t\tclog.Infof(ctx, \"Dropping never-proxy %q because it is not routed\", nps)\n\t\treturn true\n\t})\n\n\tproxy, neverProxyOverrides = subnet.Partition(subnets, func(i int, isn netip.Prefix) bool {\n\t\tfor r, rsn := range subnets {\n\t\t\tif i == r {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif subnet.Covers(rsn, isn) && rsn != isn {\n\t\t\t\tfor _, dsn := range neverProxy {\n\t\t\t\t\tif subnet.Covers(dsn, isn) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn true\n\t})\n\treturn subnet.Unique(proxy), neverProxy, neverProxyOverrides\n}\n\nfunc validateSubnets(name string, ns []netip.Prefix, allowLoopback func() bool) ([]netip.Prefix, error) {\n\tns = subnet.Unique(ns)\n\trs := make([]netip.Prefix, 0, len(ns))\n\tfor _, sn := range ns {\n\t\tif sn.Addr().IsLoopback() && !allowLoopback() {\n\t\t\treturn nil, fmt.Errorf(`%s subnet %s is a loopback subnet. It is never proxied`, name, sn)\n\t\t}\n\t\trs = append(rs, sn)\n\t}\n\treturn subnet.Unique(rs), nil\n}\n\n// alsoProxyVia will return true when the connection was made using --subnet-via all=<workload> or --subnet-via also=<workload>.\nfunc (s *session) alsoProxyVia() bool {\n\tfor _, pvx := range s.subnetViaWorkloads {\n\t\tif pvx.Subnet == \"also\" { // no need to test for \"all\". It's normalized into [\"also\", \"pods\", \"service\"]\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s *session) readAdditionalRouting(mgrInfo *manager.ClusterInfo) error {\n\tif r := mgrInfo.Routing; r != nil {\n\t\tsns, err := validateSubnets(\"also-proxy\", iputil.RPCsToPrefixes(r.AlsoProxySubnets), s.alsoProxyVia)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.alsoProxySubnets = subnet.Unique(append(s.alsoProxySubnets, sns...))\n\t\tclog.Infof(s, \"also-proxy subnets %v\", s.alsoProxySubnets)\n\n\t\tsns, err = validateSubnets(\"never-proxy\", iputil.RPCsToPrefixes(r.NeverProxySubnets), nope)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.neverProxySubnets = subnet.Unique(append(s.neverProxySubnets, sns...))\n\t\tclog.Infof(s, \"never-proxy subnets %v\", s.neverProxySubnets)\n\n\t\tsns, err = validateSubnets(\"allow-conflicting\", iputil.RPCsToPrefixes(r.AllowConflictingSubnets), nope)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.allowConflictingSubnets = subnet.Unique(append(s.allowConflictingSubnets, sns...))\n\t\tclog.Infof(s, \"allow-conflicting subnets %v\", s.allowConflictingSubnets)\n\t}\n\treturn nil\n}\n\n// hasSvcConnectivity checks the connectivity of the agent-injector server. It returns true if it can be connected, false otherwise.\nfunc (s *session) hasSvcConnectivity(info *manager.ClusterInfo) bool {\n\t// The traffic-manager service is headless, which means we can't try a GRPC connection to its ClusterIP.\n\t// Instead, we try an HTTP health check on the agent-injector server, since that one does expose a ClusterIP.\n\t// This is less precise than if we could check for our own GRPC, since /healthz is a common enough health check path,\n\t// but hopefully, the server on the other end isn't configured to respond to the hostname \"agent-injector\" if it isn't the agent-injector.\n\tif info.InjectorSvcIp == nil {\n\t\tclog.Debugf(s, \"No injector service IP given; usually this is because the traffic-manager is older than the telepresence binary.\"+\n\t\t\t\"Connectivity check for services set to pass.\")\n\t\treturn false\n\t}\n\tct := client.GetConfig(s).Timeouts().Get(client.TimeoutConnectivityCheck)\n\tif ct == 0 {\n\t\tclog.Info(s, \"Connectivity check for services disabled\")\n\t\treturn false\n\t}\n\tip := net.IP(info.InjectorSvcIp).String()\n\tport := info.InjectorSvcPort\n\tif port == 0 {\n\t\tport = 8443\n\t}\n\ttr := &http.Transport{\n\t\t// Skip checking the cert because its trust chain is loaded into a secret on the cluster; we'd fail to verify it\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\thcl := &http.Client{Transport: tr}\n\ttCtx, tCancel := context.WithTimeout(s, ct)\n\tdefer tCancel()\n\turl := iputil.JoinHostPort(ip, uint16(port))\n\turl = fmt.Sprintf(\"https://%s/healthz\", url)\n\trequest, err := http.NewRequestWithContext(tCtx, http.MethodHead, url, nil)\n\tif err != nil {\n\t\t// As far as I can tell, this error means a) that the context was cancelled before the request could be allocated, or b) that the request is misconstructed, e.g. bad method.\n\t\t// Neither of those two should really happen here (unless you set the timeout to a few microseconds, maybe), but we can't really continue. May as well route the cluster.\n\t\tclog.Errorf(s, \"Unexpected: service conn check could not build request: %v. Will route services anyway.\", err)\n\t\treturn false\n\t}\n\trequest.Header.Set(\"Host\", info.InjectorSvcHost)\n\tclog.Debugf(s, \"Performing service connectivity check on %s with Host %s and timeout %s\", url, info.InjectorSvcHost, ct)\n\tresp, err := hcl.Do(request)\n\tif err != nil {\n\t\t// This means either network errors (timeouts, failed to connect), or that the server doesn't speak HTTP.\n\t\tclog.Debugf(s, \"Will proxy services (%v)\", err)\n\t\treturn false\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tclog.Warnf(s, \"service IP %s is connectable, but did not respond as expected (status code %d).\"+\n\t\t\t\" Will proxy services, but this may interfere with your VPN routes.\", info.InjectorSvcIp, resp.StatusCode)\n\t\treturn false\n\t}\n\tclog.Info(s, \"Already connected to cluster, will not map service subnets.\")\n\treturn true\n}\n\n// hasPodConnectivity verifies connectivity to the traffic-manager in the cluster and returns true if connectivity is successful; false otherwise.\nfunc (s *session) hasPodConnectivity(info *manager.ClusterInfo) bool {\n\tif info.ManagerPodIp == nil {\n\t\treturn false\n\t}\n\tct := client.GetConfig(s).Timeouts().Get(client.TimeoutConnectivityCheck)\n\tif ct == 0 {\n\t\tclog.Info(s, \"Connectivity check for pods disabled\")\n\t\treturn false\n\t}\n\tip := net.IP(info.ManagerPodIp).String()\n\tport := info.ManagerPodPort\n\tif port == 0 {\n\t\tport = 8081 // Traffic managers before 2.8.0 didn't include the port because it was hardcoded at 8081\n\t}\n\ttCtx, tCancel := context.WithTimeout(s, ct)\n\tdefer tCancel()\n\tclog.Debugf(s, \"Performing pod connectivity check on IP %s with timeout %s\", ip, ct)\n\tconn, err := grpc.NewClient(\n\t\tnet.JoinHostPort(ip, strconv.Itoa(int(port))),\n\t\tgrpc.WithTransportCredentials(insecure.NewCredentials()),\n\t)\n\tif err != nil {\n\t\tclog.Debugf(s, \"Will proxy pods. NewClient: %v\", err)\n\t\treturn false\n\t}\n\tstate := conn.GetState()\n\tconn.Connect() // Initiate the connection\n\tdefer conn.Close()\n\n\t// Wait for the connection to reach READY state or fail\n\tfor {\n\t\tswitch state {\n\t\tcase connectivity.Ready:\n\t\t\t// Connection is established\n\t\t\t_, err = manager.NewManagerClient(conn).Version(tCtx, &empty.Empty{})\n\t\t\tif err != nil {\n\t\t\t\tif s.Err() != nil {\n\t\t\t\t\treturn false // session cancelled\n\t\t\t\t}\n\t\t\t\tclog.Warnf(s, \"Manager IP %s is connectable but not a traffic-manager instance (%v).\"+\n\t\t\t\t\t\" Will proxy pods, but this may interfere with your VPN routes.\", ip, err)\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tclog.Info(s, \"Will not proxy pods. Already connected to cluster.\")\n\t\t\treturn true\n\t\tcase connectivity.TransientFailure, connectivity.Shutdown:\n\t\t\tclog.Debugf(s, \"Will proxy pods, connection failed: state=%v\", state)\n\t\t\treturn false\n\t\tdefault:\n\t\t\tif !conn.WaitForStateChange(tCtx, state) {\n\t\t\t\t// Normal. The context timed out before the connection reached READY state.\n\t\t\t\tclog.Debug(s, \"Will proxy pods\")\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tstate = conn.GetState()\n\t\t}\n\t}\n}\n\nfunc (s *session) run(initErrs chan<- error) {\n\tdefer func() {\n\t\tclog.Info(s, \"-- session ended\")\n\t}()\n\tg := log.NewGroup(s)\n\tif err := s.Start(g, 0); err != nil {\n\t\tdefer close(initErrs)\n\t\tinitErrs <- err\n\t\treturn\n\t}\n\tclose(initErrs)\n\terr := g.Wait()\n\tif err != nil {\n\t\tclog.Errorf(s, \"session ended with error: %v\", err)\n\t}\n}\n\nfunc (s *session) Start(g log.Group, teleroutePort uint16) error {\n\tclusterCfg := client.GetConfig(s).Cluster()\n\tif clusterCfg.AgentPortForward {\n\t\tif k8s.CanPortForward(s, s.Namespace) {\n\t\t\ts.agentClients = agentpf.NewClients(s.Cluster, s.session)\n\t\t\tg.Go(\"agentPods\", func(ctx context.Context) error {\n\t\t\t\treturn s.agentClients.WatchAgentPods(s.managerClient())\n\t\t\t})\n\t\t} else {\n\t\t\tclog.Infof(s, \"Agent port-forwards are disabled. Client is not permitted to do port-forward to namespace %s\", s.Namespace)\n\t\t}\n\t}\n\tif err := s.activateProxyViaWorkloads(); err != nil {\n\t\treturn err\n\t}\n\tif s.podDaemon {\n\t\treturn nil\n\t}\n\n\tcancelDNSLock := sync.Mutex{}\n\tcancelDNS := func() {}\n\n\tg.Go(\"network\", func(ctx context.Context) error {\n\t\tdefer func() {\n\t\t\tcancelDNSLock.Lock()\n\t\t\tcancelDNS()\n\t\t\tcancelDNSLock.Unlock()\n\t\t}()\n\t\treturn s.watchClusterInfo(teleroutePort)\n\t})\n\n\tif s.agentClients == nil && len(s.subnetViaWorkloads) > 0 {\n\t\treturn fmt.Errorf(\"--proxy-via can only be used when cluster.agentPortForward is enabled\")\n\t}\n\n\t// At this point, we wait until the VIF is ready. It will be shortly after\n\t// the first ClusterInfo is received from the traffic-manager. A timeout\n\t// is needed so that we don't wait forever on a traffic-manager that has\n\t// been terminated for some reason.\n\twc, cancel := client.GetConfig(s).Timeouts().TimeoutContext(s, client.TimeoutTrafficManagerConnect)\n\tdefer cancel()\n\tselect {\n\tcase <-wc.Done():\n\t\t// Time out when waiting for the cluster info to arrive\n\t\ts.vifReady <- wc.Err()\n\t\ts.dnsServer.Stop()\n\t\treturn wc.Err()\n\tcase err := <-s.vifReady:\n\t\tif err != nil {\n\t\t\ts.dnsServer.Stop()\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Start the router and the DNS service and wait for the context\n\t// to be done. Then shut things down in order. The following happens:\n\t// 1. The DNS worker terminates (it needs the TUN device to be alive while doing that)\n\t// 2. The TUN device is closed (by the stop method). This unblocks the routerWorker's pending read on the device.\n\t// 3. The routerWorker terminates.\n\tg.Go(\"dns\", func(ctx context.Context) error {\n\t\tdefer s.stop()\n\t\tcancelDNSLock.Lock()\n\t\tctx, cancelDNS = context.WithCancel(ctx)\n\t\tcancelDNSLock.Unlock()\n\t\tvar dev vif.Device\n\t\tif s.tunVif != nil {\n\t\t\tdev = s.tunVif.Device\n\t\t}\n\t\treturn s.dnsServer.Worker(ctx, dev, s.configureDNS)\n\t})\n\n\tif s.tunVif != nil {\n\t\tg.Go(\"vif\", s.tunVif.Run)\n\t\terr := s.waitForProxyViaWorkloads()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif teleroutePort > 0 {\n\t\t\ts.teleroute, err = teleroute.StartServer(g, s.tunVif, s.routesCh, teleroutePort)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *session) stop() {\n\tif !atomic.CompareAndSwapInt32(&s.closing, 0, 1) {\n\t\t// session already stopped (or is stopping)\n\t\treturn\n\t}\n\tclog.Debug(s, \"Bringing down TUN-device\")\n\tcc, cancel := context.WithTimeout(context.WithoutCancel(s), time.Second)\n\tgo func() {\n\t\ts.handlers.CloseAll(cc)\n\t\tcancel()\n\t}()\n\t<-cc.Done()\n\tatomic.StoreInt32(&s.closing, 2)\n\n\tif s.managerConn != nil {\n\t\tclog.Debug(s, \"Closing port-forward to traffic-manager\")\n\t\t// Avoid sporadic hang when the client connection is torn down.\n\t\tcc, cancel = context.WithTimeout(context.WithoutCancel(s), time.Second)\n\t\tgo func() {\n\t\t\t_ = s.managerConn.Close()\n\t\t\tcancel()\n\t\t}()\n\t\t<-cc.Done()\n\t}\n\n\tif s.tunVif != nil {\n\t\tcc, cancel = context.WithTimeout(context.WithoutCancel(s), time.Second)\n\t\tdefer cancel()\n\t\tif err := s.tunVif.Close(cc); err != nil {\n\t\t\tclog.Errorf(s, \"unable to close %s: %v\", s.tunVif.Device.Name(), err)\n\t\t}\n\t}\n}\n\nfunc (s *session) activateProxyViaWorkloads() error {\n\tsl := len(s.subnetViaWorkloads)\n\tif sl == 0 {\n\t\treturn nil\n\t}\n\tvipSubnet := client.GetConfig(s).Routing().VirtualSubnet\n\tclog.Debugf(s, \"ProxyVIA using subnet %s\", vipSubnet)\n\n\ts.vipGenerator = vip.NewGenerator(vipSubnet)\n\ts.localTranslationSubnets = make([]agentSubnet, sl)\n\tfor _, wlName := range s.consolidateProxyViaWorkloads() {\n\t\tif s.agentClients == nil {\n\t\t\treturn errcat.User.Newf(\"Agent port-forwards are disabled. Client is not permitted to do proxy-via %s\", wlName)\n\t\t}\n\t\tclog.Debugf(s, \"Ensuring proxy-via agent in %s\", wlName)\n\t\t_, err := s.managerClient().EnsureAgent(s, &manager.EnsureAgentRequest{\n\t\t\tSession: s.session,\n\t\t\tName:    wlName,\n\t\t})\n\t\tif err != nil {\n\t\t\tif st, ok := status.FromError(err); ok {\n\t\t\t\tif st.Code() == codes.FailedPrecondition {\n\t\t\t\t\treturn errcat.User.New(st.Message())\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *session) consolidateProxyViaWorkloads() []string {\n\tdesiredVips := make(map[string][]netip.Prefix)\n\tsnCount := 0\n\tfor _, pvx := range s.subnetViaWorkloads {\n\t\tswitch pvx.Subnet {\n\t\tcase \"also\":\n\t\t\tdesiredVips[pvx.Workload] = append(desiredVips[pvx.Workload], s.alsoProxySubnets...)\n\t\t\tsnCount += len(s.alsoProxySubnets)\n\t\tcase \"pods\":\n\t\t\tdesiredVips[pvx.Workload] = append(desiredVips[pvx.Workload], s.podSubnets...)\n\t\t\tsnCount += len(s.podSubnets)\n\t\tcase \"service\":\n\t\t\tdesiredVips[pvx.Workload] = append(desiredVips[pvx.Workload], s.serviceSubnets...)\n\t\t\tsnCount += len(s.serviceSubnets)\n\t\tdefault:\n\t\t\tsn, err := netip.ParsePrefix(pvx.Subnet)\n\t\t\tif err != nil {\n\t\t\t\tclog.Warnf(s, \"unable to parse proxy-via subnet %s\", pvx.Subnet)\n\t\t\t} else {\n\t\t\t\tdesiredVips[pvx.Workload] = append(desiredVips[pvx.Workload], sn)\n\t\t\t\tsnCount++\n\t\t\t}\n\t\t}\n\t}\n\n\twlNames := make([]string, 0, len(desiredVips))\n\tlcs := make([]agentSubnet, 0, snCount)\n\tfor wlName, sns := range desiredVips {\n\t\tif wlName == \"local\" {\n\t\t\twlName = \"\"\n\t\t} else {\n\t\t\twlNames = append(wlNames, wlName)\n\t\t}\n\t\tfor _, sn := range sns {\n\t\t\tlcs = append(lcs, agentSubnet{Prefix: sn, workload: wlName})\n\t\t}\n\t}\n\ts.localTranslationSubnets = lcs\n\tclog.Debugf(s, \"Local translation subnets: %v\", s.localTranslationSubnets)\n\treturn wlNames\n}\n\nfunc (s *session) waitForProxyViaWorkloads() error {\n\twc := len(s.subnetViaWorkloads)\n\tif wc == 0 {\n\t\treturn nil\n\t}\n\tto := client.GetConfig(s).Timeouts().Get(client.TimeoutIntercept)\n\twaitCh := make(chan error)\n\n\t// Need unique workload names\n\tws := make([]string, 0, len(s.subnetViaWorkloads))\n\tfor _, svw := range s.subnetViaWorkloads {\n\t\tif svw.Workload != \"local\" {\n\t\t\tws = slice.AppendUnique(ws, svw.Workload)\n\t\t}\n\t}\n\tfor _, wl := range ws {\n\t\ts.agentClients.SetProxyVia(wl)\n\t\tclog.Debugf(s, \"Waiting for proxy-via agent in %s\", wl)\n\t\tgo func(wl string) {\n\t\t\twaitCh <- s.agentClients.WaitForWorkload(to, wl)\n\t\t}(wl)\n\t}\n\tfor _, wl := range ws {\n\t\tselect {\n\t\tcase <-s.Done():\n\t\t\treturn nil\n\t\tcase err := <-waitCh:\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"proxy-via agent in %s failed: %w\", wl, err)\n\t\t\t}\n\t\t\tclog.Debugf(s, \"Wait succeeded for proxy-via agent in %s\", wl)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *session) SetTopLevelDomains(topLevelDomains []string) {\n\ts.dnsServer.SetTopLevelDomainsAndSearchPath(s, topLevelDomains, s.Namespace)\n}\n\nfunc (s *session) SetExcludes(excludes []string) {\n\ts.dnsServer.SetExcludes(excludes)\n}\n\nfunc (s *session) SetMappings(mappings []*rpc.DNSMapping) {\n\ts.dnsServer.SetMappings(mappings)\n}\n\nfunc (s *session) translateEnvIPs(environment *rpc.Environment) *rpc.Environment {\n\tvip.TranslateEnvironmentIPs(s, environment.Env, s)\n\treturn environment\n}\n\nfunc (s *session) lookupIP(rq *rpc.LookupIPRequest) (*rpc.LookupIPResponse, error) {\n\tip, err := dns.LookupIP(s, s.localDNS, rq.Name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trsp := new(rpc.LookupIPResponse)\n\trsp.Ip, _ = ip.MarshalBinary()\n\treturn rsp, nil\n}\n\nfunc (s *session) MapsIPv4() bool {\n\tfor _, p := range s.localTranslationSubnets {\n\t\tif p.Addr().Is4() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s *session) MapsIPv6() bool {\n\tfor _, p := range s.localTranslationSubnets {\n\t\tif p.Addr().Is6() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (s *session) waitForAgentIP(ctx context.Context, request *rpc.WaitForAgentIPRequest) (*rpc.WaitForAgentIPResponse, error) {\n\tif s.agentClients == nil {\n\t\treturn nil, status.Error(codes.Unavailable, \"\")\n\t}\n\tip, ok := netip.AddrFromSlice(request.Ip)\n\tif !ok {\n\t\treturn nil, status.Error(codes.InvalidArgument, \"\")\n\t}\n\terr := s.agentClients.WaitForIP(ctx, request.Timeout.AsDuration(), ip)\n\tif err != nil {\n\t\treturn nil, grpcErrors.FromError(err, codes.Internal, err.Error())\n\t}\n\tip, err = s.GetLocalIP(ip)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &rpc.WaitForAgentIPResponse{LocalIp: ip.AsSlice()}, nil\n}\n\nfunc (s *session) ManagerVersion() semver.Version {\n\treturn s.managerVersion\n}\n\nfunc (s *session) DialTCP(ctx context.Context, addr netip.AddrPort) (conn net.Conn, err error) {\n\tvar d tunnel.Dialer\n\tif s.tunVif != nil && s.tunVif.Router.Routes(addr.Addr()) {\n\t\td = s.tunVif\n\t} else {\n\t\td = tunnel.DefaultDialer{}\n\t}\n\treturn d.DialTCP(ctx, addr)\n}\n\nfunc (s *session) DialUDP(ctx context.Context, localAddr netip.AddrPort, remoteAddr netip.AddrPort) (conn net.Conn, err error) {\n\tvar d tunnel.Dialer\n\tif s.tunVif != nil && s.tunVif.Router.Routes(remoteAddr.Addr()) {\n\t\td = s.tunVif\n\t} else {\n\t\td = tunnel.DefaultDialer{}\n\t}\n\treturn d.DialUDP(ctx, localAddr, remoteAddr)\n}\n\nfunc (s *session) MarkActivity() {\n\tselect {\n\tcase s.activity <- time.Now():\n\tdefault:\n\t}\n}\n\nfunc (s *service) ActivityWatcher(_ *empty.Empty, stream grpc.ServerStreamingServer[rpc.Activity]) error {\n\tfor {\n\t\tselect {\n\t\tcase <-stream.Context().Done():\n\t\t\treturn nil\n\t\tcase <-s.session.Done():\n\t\t\treturn nil\n\t\tcase at := <-s.activity:\n\t\t\terr := stream.Send(&rpc.Activity{Activity: timestamppb.New(at)})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/rootd/stream_creator.go",
    "content": "package rootd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nconst dnsConnTTL = 5 * time.Second\n\nfunc (s *session) isForDNS(ip netip.Addr, port uint16) bool {\n\treturn s.vifDNS.Addr() == ip && s.vifDNS.Port() == port\n}\n\n// checkRecursion checks that the given IP is not contained in any of the subnets\n// that the VIF is configured with. When that's the case, the VIF is somehow receiving\n// requests that originate from the cluster and dispatching it leads to infinite recursion.\nfunc checkRecursion(p types.Proto, ip netip.Addr, sn netip.Prefix) (err error) {\n\tif sn.Contains(ip) && ip != sn.Masked().Addr() {\n\t\terr = fmt.Errorf(\"refusing recursive %s %s dispatch from pod subnet %s\", p, ip, sn)\n\t}\n\treturn err\n}\n\nfunc (s *session) streamCreator() tunnel.StreamCreator {\n\treturn func(c context.Context, id tunnel.ConnID) (tunnel.Stream, error) {\n\t\tp := id.Protocol()\n\t\tsrcIp := id.SourceAddr()\n\t\tfor _, podSn := range s.podSubnets {\n\t\t\tif err := checkRecursion(p, srcIp, podSn); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\n\t\tdestAddr := id.DestinationAddr()\n\t\tif p == types.ProtoUDP {\n\t\t\tif s.isForDNS(destAddr, id.DestinationPort()) {\n\t\t\t\tpipeId := tunnel.NewConnID(p, id.Source(), s.localDNS)\n\t\t\t\tclog.Tracef(c, \"Intercept DNS %s to %s\", id, pipeId.Destination())\n\t\t\t\tfrom, to := tunnel.NewPipe(pipeId, tunnel.SessionID(s.session.SessionId), tunnel.DnsToTun, tunnel.TunToDNS)\n\t\t\t\ttunnel.NewDialerTTL(to, func() {}, dnsConnTTL, nil, nil).Start(c)\n\t\t\t\treturn from, nil\n\t\t\t}\n\t\t}\n\n\t\tif mp, ok := s.l4PortMap.Load(types.AddrPortProto{\n\t\t\tAddrPort: netip.AddrPortFrom(destAddr, id.DestinationPort()),\n\t\t\tProto:    id.Protocol(),\n\t\t}); ok {\n\t\t\tid = tunnel.NewConnID(id.Protocol(), id.Source(), netip.AddrPortFrom(destAddr, mp))\n\t\t}\n\n\t\tvar err error\n\t\tvar tp tunnel.Provider\n\t\tif a, ok := s.getAgentVIP(destAddr); ok {\n\t\t\t// s.agentClients is never nil when agentVIPs are used.\n\t\t\tif a.workload != \"\" {\n\t\t\t\ttp = s.agentClients.GetWorkloadClient(a.workload)\n\t\t\t\tif tp == nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unable to connect to a traffic-agent for workload %q\", a.workload)\n\t\t\t\t}\n\t\t\t\t// Replace the virtual IP with the original destination IP. This will ensure that the agent\n\t\t\t\t// dials the original destination when the tunnel is established.\n\t\t\t\tid = tunnel.NewConnID(id.Protocol(), id.Source(), netip.AddrPortFrom(a.destinationIP, id.DestinationPort()))\n\t\t\t\tclog.Debugf(c, \"Opening proxy-via %s tunnel for id %s\", a.workload, id)\n\t\t\t} else {\n\t\t\t\tclog.Debugf(c, \"Translating proxy-via %s to %s\", destAddr, a.destinationIP)\n\t\t\t\tdestAddr = a.destinationIP\n\t\t\t\tid = tunnel.NewConnID(id.Protocol(), id.Source(), netip.AddrPortFrom(destAddr, id.DestinationPort()))\n\t\t\t}\n\t\t}\n\n\t\tif tp == nil {\n\t\t\ttp = s.getAgentClient(destAddr)\n\t\t\tif tp != nil {\n\t\t\t\tclog.Debugf(c, \"Opening traffic-agent tunnel for id %s using agent %s\", id, tp)\n\t\t\t} else {\n\t\t\t\ttp = tunnel.ManagerProvider(s.managerClient())\n\t\t\t\tclog.Debugf(c, \"Opening traffic-manager tunnel for id %s\", id)\n\t\t\t}\n\t\t}\n\t\tct, err := tp.Tunnel(c)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif id.Protocol() == types.ProtoTCP {\n\t\t\ts.MarkActivity()\n\t\t}\n\n\t\ttc := client.GetConfig(c).Timeouts()\n\t\treturn tunnel.NewClientStream(\n\t\t\tc, tunnel.TunToClient, ct, id, tunnel.SessionID(s.session.SessionId), tc.Get(client.TimeoutRoundtripLatency), tc.Get(client.TimeoutEndpointDial))\n\t}\n}\n\nfunc (s *session) getAgentVIP(dest netip.Addr) (a agentVIP, ok bool) {\n\tif s.virtualIPs != nil {\n\t\ta, ok = s.virtualIPs.Load(dest)\n\t}\n\treturn a, ok\n}\n\nfunc (s *session) getAgentClient(ip netip.Addr) (pvd tunnel.Provider) {\n\tif s.agentClients != nil {\n\t\tpvd = s.agentClients.GetClient(ip)\n\t}\n\treturn pvd\n}\n"
  },
  {
    "path": "pkg/client/rootd/vip/env_nat.go",
    "content": "package vip\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"regexp\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nvar (\n\tipV4Rx = regexp.MustCompile(`(?:\\d{1,3}\\.){3}\\d{1,3}`)                                                              //nolint:gochecknoglobals // constant\n\tipV6Rx = regexp.MustCompile(`(?:[0-9a-fA-F]{0,4}:){1,7}(?:[0-9a-fA-F]{0,4}%[0-9a-zA-Z]+|(?:\\d{1,3}\\.){3}\\d{1,3}|)`) //nolint:gochecknoglobals // constant\n)\n\ntype LocalIPProvider interface {\n\tMapsIPv4() bool\n\tMapsIPv6() bool\n\tGetLocalIP(remoteIP netip.Addr) (netip.Addr, error)\n}\n\nfunc replaceIP(provider LocalIPProvider, rx *regexp.Regexp, s string) string {\n\treturn rx.ReplaceAllStringFunc(s, func(s string) string {\n\t\tif ip, err := netip.ParseAddr(s); err == nil {\n\t\t\tif rip, err := provider.GetLocalIP(ip); err == nil {\n\t\t\t\treturn rip.String()\n\t\t\t}\n\t\t}\n\t\treturn s\n\t})\n}\n\nfunc TranslateEnvironmentIPs(ctx context.Context, env map[string]string, provider LocalIPProvider) {\n\tif provider.MapsIPv4() {\n\t\tfor k, ev := range env {\n\t\t\trv := replaceIP(provider, ipV4Rx, ev)\n\t\t\tif ev != rv {\n\t\t\t\tclog.Debugf(ctx, \"%s: %s -> %s\", k, ev, rv)\n\t\t\t\tenv[k] = rv\n\t\t\t}\n\t\t}\n\t}\n\tif provider.MapsIPv6() {\n\t\tfor k, ev := range env {\n\t\t\trv := replaceIP(provider, ipV6Rx, ev)\n\t\t\tif ev != rv {\n\t\t\t\tclog.Debugf(ctx, \"%s: %s -> %s\", k, ev, rv)\n\t\t\t\tenv[k] = rv\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/client/rootd/vip/env_nat_test.go",
    "content": "package vip\n\nimport (\n\t\"maps\"\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/telepresenceio/clog/testutil\"\n)\n\ntype localIPProviderTest struct {\n\tcidrs     []netip.Prefix\n\tgenerator Generator\n\tmapped    map[netip.Addr]netip.Addr\n}\n\nfunc (l *localIPProviderTest) MapsIPv4() bool {\n\tfor _, p := range l.cidrs {\n\t\tif p.Addr().Is4() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (l *localIPProviderTest) MapsIPv6() bool {\n\tfor _, p := range l.cidrs {\n\t\tif p.Addr().Is6() {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (l *localIPProviderTest) GetLocalIP(remoteIP netip.Addr) (netip.Addr, error) {\n\tif lip, ok := l.mapped[remoteIP]; ok {\n\t\treturn lip, nil\n\t}\n\tfor _, p := range l.cidrs {\n\t\tif p.Contains(remoteIP) {\n\t\t\tlip, err := l.generator.Next()\n\t\t\tif err != nil {\n\t\t\t\treturn remoteIP, err\n\t\t\t}\n\t\t\tl.mapped[remoteIP] = lip\n\t\t\treturn lip, nil\n\t\t}\n\t}\n\treturn remoteIP, nil\n}\n\nfunc Test_translateEnvironmentIPs(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tcidr  string\n\t\tvCidr string\n\t\tip    string\n\t\twant  string\n\t}{\n\t\t{\n\t\t\t\"IPV4 URI\",\n\t\t\t\"10.110.210.0/24\",\n\t\t\t\"100.156.200.0/24\",\n\t\t\t\"tcp://10.110.210.159:80\",\n\t\t\t\"tcp://100.156.200.1:80\",\n\t\t},\n\t\t{\n\t\t\t\"IPV4\",\n\t\t\t\"10.110.210.0/24\",\n\t\t\t\"100.156.200.0/24\",\n\t\t\t\"10.110.210.159\",\n\t\t\t\"100.156.200.1\",\n\t\t},\n\t\t{\n\t\t\t\"IPV4 list\",\n\t\t\t\"10.110.210.0/24\",\n\t\t\t\"100.156.200.0/24\",\n\t\t\t`[\"10.110.210.8\", \"10.110.210.9\", \"10.110.210.9\", \"192.168.1.3\"]`,\n\t\t\t`[\"100.156.200.1\", \"100.156.200.2\", \"100.156.200.2\", \"192.168.1.3\"]`,\n\t\t},\n\t\t{\n\t\t\t\"IPV4 in IPV6\",\n\t\t\t\"::ffff:10.110.210.0/96\",\n\t\t\t\"::ffff:100.156.200.0/120\",\n\t\t\t\"::ffff:10.110.210.8\",\n\t\t\t\"::ffff:100.156.200.1\",\n\t\t},\n\t\t{\n\t\t\t\"IPV6 URI\",\n\t\t\t\"::ffff:10.110.210.0/96\",\n\t\t\t\"::ffff:100.156.200.0/120\",\n\t\t\t\"tcp://[::ffff:10.110.210.8]:53\",\n\t\t\t\"tcp://[::ffff:100.156.200.1]:53\",\n\t\t},\n\t\t{\n\t\t\t\"IPV4 leading ndot\",\n\t\t\t\"100.156.200.0/24\",\n\t\t\t\"10.110.210.0/24\",\n\t\t\t\"2.10.110.210.8\",\n\t\t\t\"2.10.110.210.8\",\n\t\t},\n\t}\n\n\tctx := testutil.NewContext(t, false)\n\tfor _, tt := range tests {\n\t\tprovider := &localIPProviderTest{\n\t\t\tgenerator: NewGenerator(netip.MustParsePrefix(tt.vCidr)),\n\t\t\tmapped:    make(map[netip.Addr]netip.Addr),\n\t\t\tcidrs:     []netip.Prefix{netip.MustParsePrefix(tt.cidr)},\n\t\t}\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tenv := map[string]string{\"key\": tt.ip}\n\t\t\twant := map[string]string{\"key\": tt.want}\n\t\t\tTranslateEnvironmentIPs(ctx, env, provider)\n\t\t\tif !maps.Equal(env, want) {\n\t\t\t\tt.Errorf(\"TranslateEnvironmentIPs() = %v, want %v\", env, want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/client/rootd/vip/vip.go",
    "content": "package vip\n\nimport (\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sync/atomic\"\n)\n\ntype Generator interface {\n\tNext() (netip.Addr, error)\n\tSubnet() netip.Prefix\n}\n\n// NewGenerator creates a generator for virtual IPs with in the given subnet.\nfunc NewGenerator(sn netip.Prefix) Generator {\n\tlo := sn.Masked().Addr()\n\tif lo.Is4() {\n\t\treturn &ip4Generator{\n\t\t\tsubnet:        sn,\n\t\t\tnextVirtualIP: intFromIPV4(lo),\n\t\t}\n\t} else {\n\t\tfixed, lo := intsFromIPV6(lo)\n\t\treturn &vip6Provider{\n\t\t\tsubnet:  sn,\n\t\t\tfixedHi: fixed,\n\t\t\tnextLo:  lo,\n\t\t}\n\t}\n}\n\ntype ip4Generator struct {\n\tsubnet        netip.Prefix\n\tnextVirtualIP uint32\n}\n\nfunc (v *ip4Generator) Next() (netip.Addr, error) {\n\tnxt := ipV4FromInt(atomic.AddUint32(&v.nextVirtualIP, 1))\n\tif !v.subnet.Contains(nxt) {\n\t\treturn netip.Addr{}, fmt.Errorf(\"virtual subnet CIDR %s is exhausted\", v.Subnet())\n\t}\n\treturn nxt, nil\n}\n\nfunc (v *ip4Generator) Subnet() netip.Prefix {\n\treturn v.subnet\n}\n\nfunc ipV4FromInt(v uint32) netip.Addr {\n\treturn netip.AddrFrom4([4]byte{\n\t\tbyte(v & 0xff000000 >> 24),\n\t\tbyte(v & 0x00ff0000 >> 16),\n\t\tbyte(v & 0x0000ff00 >> 8),\n\t\tbyte(v & 0x000000ff),\n\t})\n}\n\nfunc intFromIPV4(a netip.Addr) uint32 {\n\tv := a.As4()\n\treturn uint32(v[0])<<24 | uint32(v[1])<<16 | uint32(v[2])<<8 | uint32(v[3])\n}\n\ntype vip6Provider struct {\n\tsubnet  netip.Prefix\n\tfixedHi uint64\n\tnextLo  uint64\n}\n\nfunc (v *vip6Provider) Next() (netip.Addr, error) {\n\tnxt := ipV6FromInts(v.fixedHi, atomic.AddUint64(&v.nextLo, 1))\n\tif !v.subnet.Contains(nxt) {\n\t\treturn netip.Addr{}, fmt.Errorf(\"virtual subnet CIDR %s is exhausted\", v.Subnet())\n\t}\n\treturn nxt, nil\n}\n\nfunc (v *vip6Provider) Subnet() netip.Prefix {\n\treturn v.subnet\n}\n\nfunc ipV6FromInts(hi, lo uint64) netip.Addr {\n\treturn netip.AddrFrom16([16]byte{\n\t\tbyte(hi & 0xff00000000000000 >> 56),\n\t\tbyte(hi & 0x00ff000000000000 >> 48),\n\t\tbyte(hi & 0x0000ff0000000000 >> 40),\n\t\tbyte(hi & 0x000000ff00000000 >> 32),\n\t\tbyte(hi & 0x00000000ff000000 >> 24),\n\t\tbyte(hi & 0x0000000000ff0000 >> 16),\n\t\tbyte(hi & 0x000000000000ff00 >> 8),\n\t\tbyte(hi & 0x00000000000000ff),\n\t\tbyte(lo & 0xff00000000000000 >> 56),\n\t\tbyte(lo & 0x00ff000000000000 >> 48),\n\t\tbyte(lo & 0x0000ff0000000000 >> 40),\n\t\tbyte(lo & 0x000000ff00000000 >> 32),\n\t\tbyte(lo & 0x00000000ff000000 >> 24),\n\t\tbyte(lo & 0x0000000000ff0000 >> 16),\n\t\tbyte(lo & 0x000000000000ff00 >> 8),\n\t\tbyte(lo & 0x00000000000000ff),\n\t})\n}\n\nfunc intsFromIPV6(a netip.Addr) (uint64, uint64) {\n\tv := a.As16()\n\treturn uint64(v[0])<<56 | uint64(v[1])<<48 | uint64(v[2])<<40 | uint64(v[3])<<32 | uint64(v[4])<<24 | uint64(v[5])<<16 | uint64(v[6])<<8 | uint64(v[7]),\n\t\tuint64(v[8])<<56 | uint64(v[9])<<48 | uint64(v[10])<<40 | uint64(v[11])<<32 | uint64(v[12])<<24 | uint64(v[13])<<16 | uint64(v[14])<<8 | uint64(v[15])\n}\n"
  },
  {
    "path": "pkg/client/stream_error.go",
    "content": "package client\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// RecvEOFError should be returned when a component has returned EOF from a stream.\n// Do not use this if, for example, the initial dial to a stream fails.\ntype RecvEOFError struct {\n\tmsg string\n\terr error\n}\n\nfunc (e *RecvEOFError) Error() string {\n\treturn fmt.Sprintf(\"%s: %v\", e.msg, e.err)\n}\n\nfunc (e *RecvEOFError) Unwrap() error {\n\treturn e.err\n}\n\n// WrapRecvErr wraps an error from a Recv call. If the error is nil, nil is returned.\n// If the error indicates that the remote end has , a RecvEOFError wrapping the error will be returned.\n// Otherwise, the original error will be wrapped as fmt.Errorf(\"%s: %w\", msg, err).\nfunc WrapRecvErr(err error, msg string) error {\n\tif err == nil {\n\t\treturn nil\n\t}\n\tif status.Code(err) == codes.Unavailable || err == io.EOF {\n\t\treturn &RecvEOFError{msg, err}\n\t}\n\treturn fmt.Errorf(\"%s: %w\", msg, err)\n}\n"
  },
  {
    "path": "pkg/client/userd/daemon/grpc.go",
    "content": "package daemon\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/netip\"\n\t\"os/exec\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/bwcompat\"\n\tcliDaemon \"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/userd\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/userd/trafficmgr\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\tgrpcErrors \"github.com/telepresenceio/telepresence/v2/pkg/grpc/errors\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/server\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nfunc (s *service) FuseFTPError() error {\n\treturn s.fuseFTPError\n}\n\nfunc (s *service) withSession(ctx context.Context, f func(context.Context, userd.Session) error) (err error) {\n\ts.sessionLock.RLock()\n\tdefer s.sessionLock.RUnlock()\n\tif s.session == nil {\n\t\treturn status.Error(codes.Unavailable, \"no active session\")\n\t}\n\tselect {\n\tcase <-s.session.Done():\n\t\treturn status.Error(codes.Canceled, \"session cancelled\")\n\tdefault:\n\t\ts.session.MarkActivity()\n\t\treturn f(server.NewCombinedContext(s.session, ctx), s.session)\n\t}\n}\n\nfunc (s *service) Version(ctx context.Context, _ *empty.Empty) (*common.VersionInfo, error) {\n\treturn client.VersionInfo(ctx), nil\n}\n\nfunc (s *service) Connect(ctx context.Context, cr *rpc.ConnectRequest) (result *rpc.ConnectInfo, err error) {\n\tresult = &rpc.ConnectInfo{}\n\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) (err error) {\n\t\tresult, err = session.UpdateStatus(ctx, cr)\n\t\treturn err\n\t})\n\tif status.Code(err) != codes.Unavailable {\n\t\treturn result, err\n\t}\n\n\ts.sessionLock.Lock()\n\tdefer s.sessionLock.Unlock()\n\tif s.session != nil {\n\t\t// Someone beat us to taking the lock.\n\t\terr = s.session.CheckStatus(cr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn s.session.Status(server.NewCombinedContext(s.session, ctx))\n\t}\n\n\tvar cfg client.Config\n\tcfg, err = client.LoadConfig(s)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Obtain the kubeconfig from the request parameters so that we can determine\n\t// what kubernetes context that will be used.\n\tvar kubeConfig *k8s.Kubeconfig\n\tsessionCtx, sessionCancel := context.WithCancel(s.Context)\n\tkubeConfig, err = k8s.DaemonKubeconfig(client.WithConfig(sessionCtx, cfg), cr)\n\tif err != nil {\n\t\tsessionCancel()\n\t\tif s.rootSessionInProc {\n\t\t\ts.quit(true)\n\t\t}\n\t\tclog.Errorf(ctx, \"Failed to obtain kubeconfig: %v\", err)\n\t\treturn result, err\n\t}\n\n\t// The service must know about the clientConfig when the session is created because the session creation\n\t// will connect to the root daemon, which in turn might call back to the Authenticator service provided by\n\t// this service.\n\ts.clientConfigLock.Lock()\n\ts.clientConfig = kubeConfig.ClientConfig\n\ts.clientConfigLock.Unlock()\n\tdefer func() {\n\t\tif err != nil {\n\t\t\ts.clientConfigLock.Lock()\n\t\t\ts.clientConfig = nil\n\t\t\ts.clientConfigLock.Unlock()\n\t\t}\n\t}()\n\n\tdaemonID := cliDaemon.NewIdentifier(cr.Name, kubeConfig.KubeContext, kubeConfig.Namespace, proc.RunningInContainer())\n\twg := &sync.WaitGroup{}\n\n\tvar session userd.Session\n\tsession, result, err = trafficmgr.NewSession(s, server.NewCombinedContext(s, ctx), cr, kubeConfig, wg)\n\tif err != nil {\n\t\tsessionCancel()\n\t\tif s.rootSessionInProc {\n\t\t\t// Simplified session management. The daemon handles one session, then exits.\n\t\t\ts.quit(true)\n\t\t}\n\t\treturn nil, err\n\t}\n\tclient.ReloadLogLevel(session)\n\ts.sessionCancel = func() {\n\t\tif err := session.ClearIngestsAndIntercepts(); err != nil {\n\t\t\tclog.Errorf(ctx, \"failed to clear intercepts: %v\", err)\n\t\t}\n\t\tsessionCancel()\n\t}\n\tsessionRunning := make(chan struct{})\n\ts.session = session\n\ts.sessionRunning = sessionRunning\n\n\t// Run the session asynchronously. We must be able to respond to connect (with UpdateStatus) while\n\t// the session is running. The s.sessionCancel is called from Disconnect\n\tgo func() {\n\t\tsession.Run()\n\t\twg.Wait()\n\t\tclose(sessionRunning)\n\t\tif s.rootSessionInProc {\n\t\t\t// Simplified session management. The daemon handles one session, then exits.\n\t\t\ts.quit(false)\n\t\t}\n\t\ts.clearSession(session)\n\t}()\n\tif s.rootSessionInProc {\n\t\tgo runAliveAndCancellationSession(session, s.sessionCancel, daemonID, wg)\n\t}\n\treturn result, err\n}\n\nfunc (s *service) Disconnect(ctx context.Context, ex *empty.Empty) (*empty.Empty, error) {\n\ts.cancelSession(ctx, true)\n\treturn &empty.Empty{}, nil\n}\n\nfunc (s *service) cancelSession(ctx context.Context, disconnectRoot bool) {\n\tvar oldSession userd.Session\n\terr := s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\toldSession = session\n\t\ts.sessionCancel()\n\t\treturn nil\n\t})\n\tif err == nil && s.clearSession(oldSession) && disconnectRoot {\n\t\t_ = s.withRootDaemon(ctx, func(ctx context.Context, rd daemon.DaemonClient) error {\n\t\t\t_, _ = rd.Disconnect(ctx, &empty.Empty{})\n\t\t\treturn nil\n\t\t})\n\t}\n}\n\nfunc (s *service) clearSession(oldSession userd.Session) bool {\n\ts.sessionLock.Lock()\n\tsameSession := s.session == oldSession\n\tif sameSession {\n\t\ts.session = nil\n\t\ts.sessionCancel = nil\n\t\ts.clientConfigLock.Lock()\n\t\ts.clientConfig = nil\n\t\ts.clientConfigLock.Unlock()\n\t}\n\ts.sessionLock.Unlock()\n\tclient.ReloadLogLevel(s)\n\treturn sameSession\n}\n\nfunc (s *service) Status(ctx context.Context, ex *empty.Empty) (result *rpc.ConnectInfo, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) (err error) {\n\t\tresult, err = session.Status(ctx)\n\t\treturn err\n\t})\n\tif status.Code(err) != codes.Unavailable {\n\t\treturn result, err\n\t}\n\terr = s.withRootDaemon(ctx, func(c context.Context, dc daemon.DaemonClient) (err error) {\n\t\tif result == nil {\n\t\t\t// This may happen if the session was unavailable, which in this particular case is OK.\n\t\t\tresult = &rpc.ConnectInfo{}\n\t\t}\n\t\tresult.DaemonStatus, err = dc.Status(c, ex)\n\t\treturn err\n\t})\n\treturn result, err\n}\n\nfunc (s *service) CanIntercept(ctx context.Context, ir *rpc.CreateInterceptRequest) (empty2 *empty.Empty, err error) {\n\treturn &empty.Empty{}, s.withSession(ctx, func(ctx context.Context, session userd.Session) (err error) {\n\t\t_, err = session.CanIntercept(ctx, ir)\n\t\treturn err\n\t})\n}\n\nfunc (s *service) CreateIntercept(ctx context.Context, ir *rpc.CreateInterceptRequest) (result *manager.InterceptInfo, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) (err error) {\n\t\tresult, err = session.AddIntercept(ctx, ir)\n\t\treturn err\n\t})\n\treturn result, err\n}\n\nfunc (s *service) RemoveIntercept(ctx context.Context, rr *manager.RemoveInterceptRequest2) (*empty.Empty, error) {\n\treturn &empty.Empty{}, s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\treturn session.RemoveIntercept(rr.Name)\n\t})\n}\n\nfunc (s *service) RevokeIntercept(ctx context.Context, rr *rpc.RevokeInterceptRequest) (*empty.Empty, error) {\n\treturn &empty.Empty{}, s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\treturn session.RevokeIntercept(ctx, rr.InterceptId)\n\t})\n}\n\nfunc (s *service) AddInterceptor(ctx context.Context, interceptor *rpc.Interceptor) (*empty.Empty, error) {\n\treturn &empty.Empty{}, s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\treturn session.AddInterceptor(interceptor.InterceptId, interceptor)\n\t})\n}\n\nfunc (s *service) RemoveInterceptor(ctx context.Context, interceptor *rpc.Interceptor) (*empty.Empty, error) {\n\treturn &empty.Empty{}, s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\treturn session.RemoveInterceptor(interceptor.InterceptId)\n\t})\n}\n\nfunc (s *service) List(ctx context.Context, lr *rpc.ListRequest) (result *rpc.WorkloadInfoSnapshot, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tresult, err = session.WorkloadInfoSnapshot([]string{lr.Namespace}, lr.Filter)\n\t\treturn err\n\t})\n\treturn result, err\n}\n\nfunc (s *service) GetKnownWorkloadKinds(ctx context.Context, _ *empty.Empty) (result *manager.KnownWorkloadKinds, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tresult, err = session.ManagerClient().GetKnownWorkloadKinds(ctx, session.SessionInfo())\n\t\tif err != nil {\n\t\t\tif status.Code(err) != codes.Unimplemented {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t// Talking to an older traffic-manager, use legacy default types\n\t\t\tresult = &manager.KnownWorkloadKinds{Kinds: []manager.WorkloadInfo_Kind{\n\t\t\t\tmanager.WorkloadInfo_DEPLOYMENT,\n\t\t\t\tmanager.WorkloadInfo_REPLICASET,\n\t\t\t\tmanager.WorkloadInfo_STATEFULSET,\n\t\t\t}}\n\t\t}\n\t\treturn nil\n\t})\n\treturn result, err\n}\n\nfunc (s *service) WatchWorkloads(wr *rpc.WatchWorkloadsRequest, stream rpc.Connector_WatchWorkloadsServer) error {\n\tvar session userd.Session\n\terr := s.withSession(stream.Context(), func(_ context.Context, s userd.Session) error {\n\t\tsession = s\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn session.WatchWorkloads(wr, stream)\n}\n\nfunc (s *service) Uninstall(ctx context.Context, ur *rpc.UninstallRequest) (*empty.Empty, error) {\n\treturn &empty.Empty{}, s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\treturn session.Uninstall(ctx, ur)\n\t})\n}\n\nfunc (s *service) GetConfig(ctx context.Context, _ *empty.Empty) (cfg *rpc.ClientConfig, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tsc, err := session.GetConfig()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata, err := json.Marshal(sc)\n\t\tif err != nil {\n\t\t\treturn status.Error(codes.Internal, err.Error())\n\t\t}\n\t\tcfg = &rpc.ClientConfig{Json: data}\n\t\treturn nil\n\t})\n\treturn cfg, err\n}\n\nfunc (s *service) GatherLogs(ctx context.Context, request *rpc.LogsRequest) (result *rpc.LogsResponse, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\tresult, err = session.GatherLogs(ctx, request)\n\t\treturn err\n\t})\n\treturn result, err\n}\n\nfunc (s *service) SetLogLevel(ctx context.Context, request *rpc.LogLevelRequest) (result *empty.Empty, err error) {\n\tmrq := &manager.LogLevelRequest{\n\t\tLogLevel: request.LogLevel,\n\t\tDuration: request.Duration,\n\t}\n\tsetLocal := func() {\n\t\tvar lvl slog.Level\n\t\tlvl, err = clog.ParseLevel(request.LogLevel)\n\t\tif err != nil {\n\t\t\terr = status.Error(codes.InvalidArgument, err.Error())\n\t\t}\n\t\tduration := time.Duration(0)\n\t\tif request.Duration != nil {\n\t\t\tduration = request.Duration.AsDuration()\n\t\t}\n\t\tif err = logging.SetAndStoreTimedLevel(ctx, s.timedLogLevel, lvl, duration, client.UserDaemonName); err != nil {\n\t\t\terr = status.Error(codes.Internal, err.Error())\n\t\t} else if !s.rootSessionInProc {\n\t\t\terr = s.withRootDaemon(ctx, func(ctx context.Context, rd daemon.DaemonClient) error {\n\t\t\t\t_, err := rd.SetLogLevel(ctx, mrq)\n\t\t\t\treturn err\n\t\t\t})\n\t\t}\n\t}\n\tsetRemote := func() {\n\t\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\t\t_, err := session.ManagerClient().SetLogLevel(ctx, mrq)\n\t\t\treturn err\n\t\t})\n\t}\n\tswitch request.Scope {\n\tcase rpc.LogLevelRequest_LOCAL_ONLY:\n\t\tsetLocal()\n\tcase rpc.LogLevelRequest_REMOTE_ONLY:\n\t\tsetRemote()\n\tdefault:\n\t\tsetLocal()\n\t\tif err == nil {\n\t\t\tsetRemote()\n\t\t}\n\t}\n\treturn &empty.Empty{}, err\n}\n\nfunc (s *service) Quit(ctx context.Context, ex *empty.Empty) (qr *daemon.QuitResponse, err error) {\n\ts.cancelSession(ctx, false)\n\ts.quit(false)\n\terr = s.withRootDaemon(context.WithoutCancel(ctx), func(ctx context.Context, rd daemon.DaemonClient) (err error) {\n\t\tclog.Debug(ctx, \"Telling root daemon to Quit\")\n\t\tqr, err = rd.Quit(ctx, ex)\n\t\treturn err\n\t})\n\tif err != nil {\n\t\tqr = &daemon.QuitResponse{}\n\t\terr = nil\n\t}\n\treturn qr, err\n}\n\nfunc (s *service) RemoteMountAvailability(ctx context.Context, ex *empty.Empty) (*empty.Empty, error) {\n\tif proc.RunningInContainer() {\n\t\t// We mount using docker volumes and the telemount driver plugin.\n\t\treturn ex, nil\n\t}\n\tif client.GetConfig(ctx).Intercept().UseFtp {\n\t\treturn ex, s.FuseFTPError()\n\t}\n\n\t// Use CombinedOutput to include stderr which has information about whether they\n\t// need to upgrade to a newer version of macFUSE or not\n\tvar cmd *exec.Cmd\n\tif runtime.GOOS == \"windows\" {\n\t\tcmd = proc.CommandContext(ctx, \"sshfs-win\", \"cmd\", \"-V\")\n\t} else {\n\t\tcmd = proc.CommandContext(ctx, \"sshfs\", \"-V\")\n\t}\n\tout, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"sshfs not installed: %v\", err)\n\t\treturn ex, errcat.User.New(\"sshfs is not installed on your local machine\")\n\t}\n\n\t// OSXFUSE changed to macFUSE, and we've noticed that older versions of OSXFUSE\n\t// can cause browsers to hang + kernel crashes, so we add an error to prevent\n\t// our users from running into this problem.\n\t// OSXFUSE isn't included in the output of sshfs -V in versions of 4.0.0 so\n\t// we check for that as a proxy for if they have the right version or not.\n\tif bytes.Contains(out, []byte(\"OSXFUSE\")) {\n\t\treturn ex, errcat.User.New(`macFUSE 4.0.5 or higher is required on your local machine`)\n\t}\n\treturn ex, nil\n}\n\nfunc (s *service) GetNamespaces(ctx context.Context, req *rpc.GetNamespacesRequest) (*rpc.GetNamespacesResponse, error) {\n\tvar resp rpc.GetNamespacesResponse\n\terr := s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tresp.Namespaces = session.GetCurrentNamespaces(req.ForClientAccess)\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif p := req.Prefix; p != \"\" {\n\t\tvar namespaces []string\n\t\tfor _, namespace := range resp.Namespaces {\n\t\t\tif strings.HasPrefix(namespace, p) {\n\t\t\t\tnamespaces = append(namespaces, namespace)\n\t\t\t}\n\t\t}\n\t\tresp.Namespaces = namespaces\n\t}\n\n\treturn &resp, nil\n}\n\nfunc (s *service) TrafficManagerVersion(ctx context.Context, _ *empty.Empty) (vi *common.VersionInfo, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tvi = &common.VersionInfo{Name: session.ManagerName(), Version: \"v\" + session.ManagerVersion().String()}\n\t\treturn nil\n\t})\n\treturn vi, err\n}\n\nfunc (s *service) RootDaemonVersion(ctx context.Context, empty *empty.Empty) (vi *common.VersionInfo, err error) {\n\tif s.rootSessionInProc {\n\t\treturn client.VersionInfo(ctx), nil\n\t}\n\terr = s.withRootDaemon(ctx, func(ctx context.Context, rd daemon.DaemonClient) error {\n\t\tvi, err = rd.Version(s, empty)\n\t\treturn err\n\t})\n\treturn vi, err\n}\n\nfunc (s *service) AgentImageFQN(ctx context.Context, empty *empty.Empty) (fqn *manager.AgentImageFQN, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\tfqn, err = session.ManagerClient().GetAgentImageFQN(ctx, empty)\n\t\treturn err\n\t})\n\treturn fqn, err\n}\n\nfunc (s *service) GetAgentConfig(ctx context.Context, request *manager.AgentConfigRequest) (rsp *manager.AgentConfigResponse, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\trequest.Session = session.SessionInfo()\n\t\trsp, err = session.ManagerClient().GetAgentConfig(ctx, request)\n\t\treturn err\n\t})\n\treturn rsp, err\n}\n\nfunc (s *service) GetClusterSubnets(ctx context.Context, _ *empty.Empty) (cs *rpc.ClusterSubnets, err error) {\n\tvar podSubnets, svcSubnets []*manager.IPNet\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\t// The manager can sometimes send the different subnets in different Sends,\n\t\t// but after 5 seconds of listening to it, we should expect to have everything\n\t\ttCtx, tCancel := context.WithTimeout(ctx, 5*time.Second)\n\t\tdefer tCancel()\n\t\tinfoStream, err := session.ManagerClient().WatchClusterInfo(tCtx, session.SessionInfo())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor {\n\t\t\tmgrInfo, err := infoStream.Recv()\n\t\t\tif err != nil {\n\t\t\t\tif tCtx.Err() != nil || errors.Is(err, io.EOF) {\n\t\t\t\t\terr = nil\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tbwcompat.FixLegacyClusterInfo(mgrInfo)\n\t\t\tfor _, sn := range mgrInfo.ServiceCidrs {\n\t\t\t\tvar sb netip.Prefix\n\t\t\t\terr = sb.UnmarshalBinary(sn)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tsvcSubnets = append(svcSubnets, iputil.PrefixToRPC(sb))\n\t\t\t}\n\t\t\tpodSubnets = append(podSubnets, mgrInfo.PodSubnets...)\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &rpc.ClusterSubnets{PodSubnets: podSubnets, SvcSubnets: svcSubnets}, nil\n}\n\nfunc (s *service) GetIntercept(ctx context.Context, request *manager.GetInterceptRequest) (ii *manager.InterceptInfo, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tii = session.GetInterceptInfo(request.Name)\n\t\tif ii == nil {\n\t\t\treturn status.Errorf(codes.NotFound, \"found no intercept named %s\", request.Name)\n\t\t}\n\t\treturn nil\n\t})\n\treturn ii, err\n}\n\nfunc (s *service) SetDNSExcludes(ctx context.Context, req *daemon.SetDNSExcludesRequest) (*empty.Empty, error) {\n\terr := s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\treturn session.WithRootClient(ctx, func(ctx context.Context, rd daemon.DaemonClient) (err error) {\n\t\t\t_, err = rd.SetDNSExcludes(ctx, req)\n\t\t\treturn err\n\t\t})\n\t})\n\treturn &empty.Empty{}, err\n}\n\nfunc (s *service) SetDNSMappings(ctx context.Context, req *daemon.SetDNSMappingsRequest) (*empty.Empty, error) {\n\terr := s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\treturn session.WithRootClient(ctx, func(ctx context.Context, rd daemon.DaemonClient) (err error) {\n\t\t\t_, err = rd.SetDNSMappings(ctx, req)\n\t\t\treturn err\n\t\t})\n\t})\n\treturn &empty.Empty{}, err\n}\n\nfunc (s *service) Ingest(ctx context.Context, request *rpc.IngestRequest) (response *rpc.IngestInfo, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\tresponse, err = session.Ingest(ctx, request)\n\t\treturn err\n\t})\n\treturn response, err\n}\n\nfunc (s *service) GetIngest(ctx context.Context, request *rpc.IngestIdentifier) (response *rpc.IngestInfo, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tresponse, err = session.GetIngest(request)\n\t\treturn err\n\t})\n\treturn response, err\n}\n\nfunc (s *service) LeaveIngest(ctx context.Context, request *rpc.IngestIdentifier) (response *rpc.IngestInfo, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tresponse, err = session.LeaveIngest(request)\n\t\treturn err\n\t})\n\treturn response, err\n}\n\nfunc (s *service) ResolveSyntheticIP(ctx context.Context, request *rpc.ResolveSyntheticRequest) (response *rpc.ResolveSyntheticResponse, err error) {\n\terr = s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tip, ok := netip.AddrFromSlice(request.Ip)\n\t\tif !ok {\n\t\t\treturn status.Errorf(codes.InvalidArgument, \"invalid IP\")\n\t\t}\n\t\tn := session.ResolveName(ip)\n\t\tif n == \"\" {\n\t\t\treturn status.Errorf(codes.NotFound, \"found no match for synthetic IP %s\", ip)\n\t\t}\n\t\tresponse = &rpc.ResolveSyntheticResponse{Name: n}\n\t\tif !request.NameOnly {\n\t\t\tip, err = session.Resolve(ip)\n\t\t\tif err != nil {\n\t\t\t\tresponse = nil\n\t\t\t\treturn status.Error(codes.NotFound, err.Error())\n\t\t\t}\n\t\t\tresponse.ResolvedIp = ip.AsSlice()\n\t\t}\n\t\treturn nil\n\t})\n\treturn response, err\n}\n\nfunc (s *service) LookupIP(ctx context.Context, request *daemon.LookupIPRequest) (rsp *daemon.LookupIPResponse, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\treturn session.WithRootClient(ctx, func(ctx context.Context, rd daemon.DaemonClient) (err error) {\n\t\t\trsp, err = rd.LookupIP(ctx, request)\n\t\t\treturn err\n\t\t})\n\t})\n\treturn rsp, err\n}\n\nfunc (s *service) ResolvePort(ctx context.Context, request *daemon.ResolvePortRequest) (rsp *daemon.ResolvePortResponse, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\treturn session.WithRootClient(ctx, func(ctx context.Context, rd daemon.DaemonClient) (err error) {\n\t\t\trsp, err = rd.ResolvePort(ctx, request)\n\t\t\treturn err\n\t\t})\n\t})\n\treturn rsp, err\n}\n\nfunc (s *service) RerouteLocalPort(ctx context.Context, request *daemon.ReroutePortRequest) (*empty.Empty, error) {\n\terr := s.withSession(ctx, func(_ context.Context, session userd.Session) error {\n\t\tvar ap types.AddrPortProto\n\t\tif err := ap.UnmarshalBinary(request.DstHostPort); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tsession.RerouteLocalPort(ap, uint16(request.SrcPort))\n\t\treturn nil\n\t})\n\treturn &empty.Empty{}, err\n}\n\nfunc (s *service) RerouteRemotePort(ctx context.Context, request *daemon.ReroutePortRequest) (rsp *empty.Empty, err error) {\n\terr = s.withSession(ctx, func(ctx context.Context, session userd.Session) error {\n\t\treturn session.WithRootClient(ctx, func(ctx context.Context, rd daemon.DaemonClient) (err error) {\n\t\t\trsp, err = rd.RerouteRemotePort(ctx, request)\n\t\t\treturn err\n\t\t})\n\t})\n\treturn rsp, err\n}\n\nfunc (s *service) withRootDaemon(ctx context.Context, f func(ctx context.Context, daemonClient daemon.DaemonClient) error) error {\n\ts.sessionLock.RLock()\n\tdefer s.sessionLock.RUnlock()\n\tif s.session != nil {\n\t\treturn s.session.WithRootClient(ctx, f)\n\t}\n\n\tif s.rootSessionInProc {\n\t\treturn status.Error(codes.Unavailable, \"root daemon is embedded\")\n\t}\n\tconn, err := cliDaemon.DialRootDaemon(ctx, false)\n\tif err == nil {\n\t\tdefer conn.Close()\n\t\terr = f(ctx, daemon.NewDaemonClient(conn))\n\t}\n\tif err != nil {\n\t\terr = grpcErrors.FromError(err, codes.Internal, fmt.Sprintf(\"root daemon: %s\", err.Error()))\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/userd/daemon/service.go",
    "content": "package daemon\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/pflag\"\n\t\"google.golang.org/grpc\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\tauthGrpc \"github.com/telepresenceio/telepresence/v2/pkg/authenticator/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/logging\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/remotefs\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/userd\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/server\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/pprof\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/sigctx\"\n)\n\nfunc help() string {\n\treturn `The Telepresence User Daemon is a background component that manages a connection.\n\nLaunch the daemon with:\n    telepresence connect\n\nExamine the daemon's log output in\n    ` + filepath.Join(filelocation.AppUserLogDir(context.Background()), \"connector.log\") + `\nto troubleshoot problems.\n`\n}\n\n// service represents the long-running state of the Telepresence User Daemon.\ntype service struct {\n\tcontext.Context\n\trpc.UnsafeConnectorServer\n\tsrv           *grpc.Server\n\ttimedLogLevel log.TimedLevel\n\tfuseFTPError  error\n\n\t// The quit function that quits the server.\n\tquit func(sessionIsLocked bool)\n\n\tclientConfigLock sync.Mutex\n\tclientConfig     clientcmd.ClientConfig\n\n\tsessionLock    sync.RWMutex\n\tsession        userd.Session\n\tsessionCancel  context.CancelFunc\n\tsessionRunning chan struct{}\n\n\tfuseFtpMgr remotefs.FuseFTPManager\n\n\t// Run root session in-process\n\trootSessionInProc bool\n\n\t// The TCP address that the daemon listens to. Will be nil if the daemon listens to a unix socket.\n\tdaemonAddress netip.AddrPort\n\n\t// Port where root daemon (or rather the embedded root daemon) starts the teleroute service.\n\tteleroutePort uint16\n}\n\nfunc (s *service) ClientConfig() (clientcmd.ClientConfig, error) {\n\ts.clientConfigLock.Lock()\n\tcc := s.clientConfig\n\ts.clientConfigLock.Unlock()\n\tif cc == nil {\n\t\treturn nil, errors.New(\"user daemon has no client config\")\n\t}\n\treturn cc, nil\n}\n\nfunc NewService(ctx context.Context, cancel context.CancelFunc, cfg client.Config, srv *grpc.Server) userd.Service {\n\treturn newService(ctx, cancel, cfg, srv)\n}\n\nfunc newService(ctx context.Context, cancel context.CancelFunc, cfg client.Config, srv *grpc.Server) *service {\n\ts := &service{\n\t\tContext:        ctx,\n\t\tsrv:            srv,\n\t\ttimedLogLevel:  log.NewTimedLevel(cfg.LogLevels().UserDaemon, clog.SetTreeLevel),\n\t\tfuseFtpMgr:     remotefs.NewFuseFTPManager(),\n\t\tsessionRunning: make(chan struct{}),\n\t}\n\tclose(s.sessionRunning)\n\ts.quit = func(sessionIsLocked bool) {\n\t\tcancel()\n\t\tvar sessionRunning <-chan struct{}\n\t\tif sessionIsLocked {\n\t\t\tsessionRunning = s.sessionRunning\n\t\t} else {\n\t\t\ts.sessionLock.RLock()\n\t\t\tsessionRunning = s.sessionRunning\n\t\t\ts.sessionLock.RUnlock()\n\t\t}\n\t\t<-sessionRunning\n\t}\n\tif srv != nil {\n\t\t// The podd daemon never registers the gRPC servers\n\t\trpc.RegisterConnectorServer(srv, s)\n\t\tauthGrpc.RegisterAuthenticatorServer(srv, s)\n\t} else {\n\t\ts.rootSessionInProc = true\n\t}\n\treturn s\n}\n\nfunc (s *service) ConnectorServer() rpc.ConnectorServer {\n\treturn s\n}\n\nfunc (s *service) SetListenerAddress(addr netip.AddrPort) {\n\ts.daemonAddress = addr\n}\n\nfunc (s *service) ListenerAddress() netip.AddrPort {\n\treturn s.daemonAddress\n}\n\nfunc (s *service) FuseFTPMgr() remotefs.FuseFTPManager {\n\treturn s.fuseFtpMgr\n}\n\nfunc (s *service) RootSessionInProcess() bool {\n\treturn s.rootSessionInProc\n}\n\nfunc (s *service) TeleroutePort() uint16 {\n\treturn s.teleroutePort\n}\n\nfunc (s *service) Server() *grpc.Server {\n\treturn s.srv\n}\n\nconst (\n\tnameFlag          = \"name\"\n\taddressFlag       = \"address\"\n\tembedNetworkFlag  = \"embed-network\"\n\tpprofFlag         = \"pprof\"\n\tteleroutePortFlag = \"teleroute-port\"\n\tlogfileFlag       = \"logfile\"\n)\n\n// Command returns the CLI sub-command for \"userd\".\nfunc Command(ctx context.Context) *cobra.Command {\n\tc := &cobra.Command{\n\t\tUse:    client.UserDaemonName,\n\t\tShort:  \"Launch Telepresence User Daemon\",\n\t\tArgs:   cobra.ExactArgs(0),\n\t\tHidden: true,\n\t\tLong:   help(),\n\t\tRunE:   run,\n\t}\n\tflags := c.Flags()\n\tflags.String(nameFlag, client.UserDaemonName, \"Daemon name\")\n\tflags.String(logfileFlag, filepath.Join(filelocation.AppUserLogDir(ctx), \"connector.log\"),\n\t\t`Log file to write to { <path to a file> | \"stdout\" | \"stderr\" | \"-\" (same as \"stderr\") }`)\n\tflags.String(addressFlag, \"\", \"TCP Address to listen to\")\n\tflags.Bool(embedNetworkFlag, false, \"Embed network functionality in the user daemon. Requires capability NET_ADMIN\")\n\tflags.Uint16(pprofFlag, 0, \"start pprof server on the given port\")\n\tflags.Uint16(teleroutePortFlag, 0, \"start teleroute server on the given port\")\n\treturn c\n}\n\nfunc (s *service) configReload(c context.Context) error {\n\t// Ensure that the directory to watch exists.\n\tif err := os.MkdirAll(filepath.Dir(client.GetConfigFile(c)), 0o755); err != nil {\n\t\treturn err\n\t}\n\treturn client.WatchConfig(c, func(ctx context.Context) error {\n\t\ts.sessionLock.RLock()\n\t\tdefer s.sessionLock.RUnlock()\n\t\tif s.session == nil {\n\t\t\tclient.ReloadLogLevel(ctx)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc runAliveAndCancellationSession(ctx context.Context, cancel context.CancelFunc, daemonID *daemon.Identifier, wg *sync.WaitGroup) {\n\twg.Add(1)\n\tdefer wg.Done()\n\tg := log.NewGroup(ctx)\n\trunAliveAndCancellation(g, cancel, daemonID.InfoFileName(), \"-\"+daemonID.String())\n\tif err := g.Wait(); err != nil {\n\t\tclog.Error(ctx, err)\n\t}\n}\n\nfunc runAliveAndCancellation(g log.Group, cancel context.CancelFunc, daemonInfoFile, groupNameSuffix string) {\n\tg.Go(fmt.Sprintf(\"info-kicker%s\", groupNameSuffix), func(ctx context.Context) error {\n\t\t// Ensure that the daemon info file is kept up to date. This tells clients that we're alive.\n\t\treturn daemon.NewUserInfoLoader(ctx).KeepInfoAlive(daemonInfoFile)\n\t})\n\tg.Go(fmt.Sprintf(\"info-watcher%s\", groupNameSuffix), func(ctx context.Context) error {\n\t\t// Cancel the session if the daemon info file is removed.\n\t\til := daemon.NewUserInfoLoader(ctx)\n\t\treturn il.WatchInfos(func(ctx context.Context) error {\n\t\t\tok, err := il.InfoExists(daemonInfoFile)\n\t\t\tif err == nil && !ok {\n\t\t\t\tclog.Debugf(ctx, \"info-watcher cancels everything because daemon info %s does not exist\", daemonInfoFile)\n\t\t\t\tcancel()\n\t\t\t}\n\t\t\treturn err\n\t\t}, daemonInfoFile)\n\t})\n}\n\n// run is the main function when executing as the connector.\nfunc run(cmd *cobra.Command, _ []string) error {\n\treturn sigctx.DoWithSignalHandler(cmd.Context(), func(ctx context.Context) error {\n\t\treturn internalRun(ctx, cmd.Flags())\n\t})\n}\n\nfunc internalRun(c context.Context, flags *pflag.FlagSet) error {\n\tcfg, err := client.LoadConfig(c)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to load config: %w\", err)\n\t}\n\tc = client.WithConfig(c, cfg)\n\n\t// Listen on domain unix domain socket or windows named pipe. The listener must be opened\n\t// before other tasks because the CLI client will only wait for a short period of time for\n\t// the connection/socket/pipe to appear before it gives up.\n\tif pprofPort, _ := flags.GetUint16(pprofFlag); pprofPort > 0 {\n\t\tgo func() {\n\t\t\tif err := pprof.PprofServer(c, pprofPort); err != nil {\n\t\t\t\tclog.Error(c, err)\n\t\t\t}\n\t\t}()\n\t}\n\taddrFlag := flags.Lookup(addressFlag)\n\tif !addrFlag.Changed {\n\t\treturn fmt.Errorf(\"must specify %s\", addressFlag)\n\t}\n\taddrStr := addrFlag.Value.String()\n\n\tlogFile := flags.Lookup(logfileFlag).Value.String()\n\tc, err = logging.InitContext(c, logFile, cfg.LogLevels().UserDaemon, logging.RotateDaily, true)\n\tif err != nil {\n\t\treturn err\n\t}\n\tname, _ := flags.GetString(nameFlag)\n\tif name == \"\" {\n\t\tname = client.UserDaemonName\n\t}\n\tc = clog.WithGroup(c, name)\n\n\tc = docker.EnableClient(c)\n\n\trootSessionInProc, _ := flags.GetBool(embedNetworkFlag)\n\tlc := net.ListenConfig{}\n\tgrpcListener, err := lc.Listen(c, \"tcp\", addrStr)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer grpcListener.Close()\n\n\tdaemonAddress := grpcListener.Addr().(interface{ AddrPort() netip.AddrPort }).AddrPort()\n\tclog.Debugf(c, \"Listener opened on %s\", grpcListener.Addr())\n\n\tclog.Info(c, \"---\")\n\tclog.Infof(c, \"Telepresence User Daemon %s starting...\", client.DisplayVersion())\n\tclog.Infof(c, \"PID is %d\", os.Getpid())\n\tclog.Info(c, \"\")\n\n\tc, svcCancel := context.WithCancel(c)\n\tg := log.NewGroup(c)\n\n\t// Start services from within a group routine so that it gets proper cancellation\n\t// when the group is cancelled.\n\tsiCh := make(chan *service)\n\tg.Go(\"serve-grpc\", func(c context.Context) error {\n\t\t// svcCancel is what a `quit -s` call will cancel. It cancels the group.\n\t\tvar opts []grpc.ServerOption\n\t\tif mz := cfg.Grpc().MaxReceiveSize(); mz > 0 {\n\t\t\topts = append(opts, grpc.MaxRecvMsgSize(int(mz)))\n\t\t}\n\t\tsvc := server.New(c, opts...)\n\t\tsiCh <- newService(c, svcCancel, cfg, svc)\n\t\tclose(siCh)\n\t\treturn server.Serve(c, svc, grpcListener)\n\t})\n\n\ts, ok := <-siCh\n\tif !ok {\n\t\t// Return error from the \"service\" go routine\n\t\treturn g.Wait()\n\t}\n\n\ts.rootSessionInProc = rootSessionInProc\n\ts.daemonAddress = daemonAddress\n\tif tp, err := flags.GetUint16(teleroutePortFlag); err == nil && tp > 0 {\n\t\tclog.Debugf(c, \"Using teleroute %d\", tp)\n\t\ts.teleroutePort = tp\n\t}\n\n\tif err := logging.LoadTimedLevelFromCache(c, s.timedLogLevel, client.UserDaemonName); err != nil {\n\t\treturn err\n\t}\n\n\tif cfg.Intercept().UseFtp && !s.fuseFtpMgr.LinkedFTP() {\n\t\tg.Go(\"fuseftp-server\", func(c context.Context) error {\n\t\t\tif err := s.InitFTPServer(c); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\t<-c.Done()\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tg.Go(\"config-reload\", s.configReload)\n\tif !rootSessionInProc {\n\t\t// User daemon process survives multiple sessions.\n\t\trunAliveAndCancellation(g, svcCancel, daemon.InfoFileName, \"\")\n\t}\n\n\terr = g.Wait()\n\tif err != nil {\n\t\tclog.Error(c, err)\n\t}\n\treturn err\n}\n\nfunc (s *service) LinkedFTP() bool {\n\treturn s.fuseFtpMgr.LinkedFTP()\n}\n\nfunc (s *service) InitFTPServer(ctx context.Context) error {\n\treturn s.fuseFtpMgr.DeferInit(ctx)\n}\n"
  },
  {
    "path": "pkg/client/userd/service.go",
    "content": "package userd\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\n\t\"google.golang.org/grpc\"\n\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/remotefs\"\n)\n\n// A Service is one that runs during the entire lifecycle of the daemon.\n// This should be used to augment the daemon with GRPC services.\ntype Service interface {\n\t// ListenerAddress returns the address that this service is listening to.\n\tListenerAddress() netip.AddrPort\n\n\tSetListenerAddress(addr netip.AddrPort)\n\n\tServer() *grpc.Server\n\n\tConnectorServer() rpc.ConnectorServer\n\n\t// FuseFTPMgr returns the manager responsible for creating a client that can connect to the FuseFTP service.\n\tFuseFTPMgr() remotefs.FuseFTPManager\n\n\tRootSessionInProcess() bool\n\tTeleroutePort() uint16\n\n\tLinkedFTP() bool\n\n\tInitFTPServer(context.Context) error\n}\n"
  },
  {
    "path": "pkg/client/userd/session.go",
    "content": "package userd\n\nimport (\n\t\"context\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\trootdRpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/restapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype ConnectRequest interface {\n\tRequest() *rpc.ConnectRequest\n}\n\ntype WatchWorkloadsStream interface {\n\tSend(*rpc.WorkloadInfoSnapshot) error\n\tContext() context.Context\n}\n\ntype InterceptInfo interface {\n\tPreparedIntercept() *manager.PreparedIntercept\n\tPortIdentifier() (types.PortIdentifier, error)\n}\n\ntype KubeConfig interface {\n\tcontext.Context\n\tGetKubeContext() string\n\tGetRestConfig() *rest.Config\n\tGetClientConfig() clientcmd.ClientConfig\n}\n\ntype Session interface {\n\tKubeConfig\n\trestapi.AgentState\n\ttunnel.SyntheticIPResolver\n\tAddIntercept(context.Context, *rpc.CreateInterceptRequest) (*manager.InterceptInfo, error)\n\tAddInterceptor(string, *rpc.Interceptor) error\n\tCanIntercept(context.Context, *rpc.CreateInterceptRequest) (InterceptInfo, error)\n\tClearIngestsAndIntercepts() error\n\tGatherLogs(context.Context, *rpc.LogsRequest) (*rpc.LogsResponse, error)\n\tGetConfig() (*client.SessionConfig, error)\n\tGetCurrentNamespaces(forClientAccess bool) []string\n\tGetIngest(*rpc.IngestIdentifier) (*rpc.IngestInfo, error)\n\tGetInterceptInfo(string) *manager.InterceptInfo\n\tGetInterceptSpec(string) *manager.InterceptSpec\n\tGetService() Service\n\tIngest(context.Context, *rpc.IngestRequest) (*rpc.IngestInfo, error)\n\tInterceptsForWorkload(string, string) []*manager.InterceptSpec\n\tLeaveIngest(*rpc.IngestIdentifier) (*rpc.IngestInfo, error)\n\tManagerClient() manager.ManagerClient\n\tManagerName() string\n\tManagerVersion() semver.Version\n\tMarkActivity()\n\tRemoveIntercept(string) error\n\tRemoveInterceptor(string) error\n\tRerouteLocalPort(ap types.AddrPortProto, srcPort uint16)\n\n\t// WithRootClient calls the given function with a gRPC-cancel sensitive context and the root daemon client.\n\t// This function is intended to facilitate gRPC calls to the root daemon that are sensitive to both the session\n\t// context and the context from the originating gRPC call to the user daemon.\n\tWithRootClient(context.Context, func(context.Context, rootdRpc.DaemonClient) error) error\n\n\tRun()\n\tSessionInfo() *manager.SessionInfo\n\tStatus(context.Context) (*rpc.ConnectInfo, error)\n\tUninstall(context.Context, *rpc.UninstallRequest) error\n\tCheckStatus(request *rpc.ConnectRequest) error\n\tUpdateStatus(context.Context, *rpc.ConnectRequest) (*rpc.ConnectInfo, error)\n\tWatchWorkloads(*rpc.WatchWorkloadsRequest, WatchWorkloadsStream) error\n\tWorkloadInfoSnapshot([]string, rpc.ListRequest_Filter) (*rpc.WorkloadInfoSnapshot, error)\n\tRevokeIntercept(ctx context.Context, interceptID string) error\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/agents.go",
    "content": "package trafficmgr\n\nimport (\n\t\"context\"\n\t\"slices\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/watcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n)\n\nfunc (s *session) watchAgentsLoop(ctx context.Context) error {\n\tsnapMap := make(map[string]*manager.AgentInfo)\n\terr := watcher.WatchWithRetry(ctx, \"WatchAgentsDelta\", client.GetConfig(ctx).Grpc().WatchRetryInterval,\n\t\tfunc(ctx context.Context) (grpc.ServerStreamingClient[manager.AgentInfoDelta], error) {\n\t\t\treturn s.ManagerClient().WatchAgentsDelta(ctx, s.SessionInfo())\n\t\t},\n\t\tfunc(delta *manager.AgentInfoDelta) error {\n\t\t\tmaps.DeltaUpdate(snapMap, delta.Upserts, delta.Removals)\n\t\t\ts.handleAgentSnapshot(ctx, maps.Values(snapMap))\n\t\t\treturn nil\n\t\t}, func() error {\n\t\t\tclear(snapMap)\n\t\t\treturn nil\n\t\t})\n\n\tif err != nil && status.Code(err) == codes.Unimplemented {\n\t\tclog.Warnf(ctx, \"WatchAgentsDelta is not implemented by the traffic-manager, falling back to WatchAgents and full snapshots\")\n\t\terr = watcher.WatchWithRetry(ctx, \"WatchAgents\", client.GetConfig(ctx).Grpc().WatchRetryInterval,\n\t\t\tfunc(ctx context.Context) (grpc.ServerStreamingClient[manager.AgentInfoSnapshot], error) {\n\t\t\t\treturn s.ManagerClient().WatchAgents(ctx, s.SessionInfo())\n\t\t\t},\n\t\t\tfunc(snapshot *manager.AgentInfoSnapshot) error {\n\t\t\t\ts.handleAgentSnapshot(ctx, snapshot.Agents)\n\t\t\t\treturn nil\n\t\t\t}, nil)\n\t}\n\t// Handle as if we had an empty snapshot. This will ensure that port forwards and volume mounts are canceled correctly.\n\ts.handleAgentSnapshot(ctx, nil)\n\treturn err\n}\n\nfunc (s *session) handleAgentSnapshot(ctx context.Context, infos []*manager.AgentInfo) {\n\ts.ingestTracker.initSnapshot()\n\ts.setCurrentAgents(infos)\n\n\t// infoForKey returns the AgentInfos that matches the ingestKey\n\tinfosForKey := func(key ingestKey) (ais []*manager.AgentInfo) {\n\t\tfor _, info := range infos {\n\t\t\tif info.Name == key.workload {\n\t\t\t\tfor cn := range info.Containers {\n\t\t\t\t\tif cn == key.container {\n\t\t\t\t\t\tais = append(ais, info)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn ais\n\t}\n\n\ts.currentIngests.Range(func(key ingestKey, ig *ingest) bool {\n\t\tais := infosForKey(key)\n\t\tif len(ais) > 0 {\n\t\t\tif slices.IndexFunc(ais, func(cai *manager.AgentInfo) bool { return cai.PodName == ig.PodName }) < 0 {\n\t\t\t\t// The pod selected for the ingest is no longer active, so replace it.\n\t\t\t\tai := ais[0]\n\t\t\t\terr := s.translateContainerEnv(ctx, ai, ig.container)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"failed to translate container env: %v\", err)\n\t\t\t\t}\n\t\t\t\tig.AgentInfo = ai\n\t\t\t}\n\t\t\ts.ingestTracker.start(ig.podAccess(s.rootDaemon))\n\t\t}\n\t\treturn true\n\t})\n\ts.ingestTracker.cancelUnwanted(ctx)\n}\n\nfunc (s *session) getCurrentAgents() []*manager.AgentInfo {\n\ts.currentInterceptsLock.Lock()\n\tagents := s.currentAgents\n\ts.currentInterceptsLock.Unlock()\n\treturn agents\n}\n\nfunc (s *session) setCurrentAgents(agents []*manager.AgentInfo) {\n\ts.currentInterceptsLock.Lock()\n\ts.currentAgents = agents\n\ts.currentInterceptsLock.Unlock()\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/config.go",
    "content": "package trafficmgr\n\nimport (\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n)\n\nfunc (s *session) GetConfig() (*client.SessionConfig, error) {\n\treturn &client.SessionConfig{\n\t\tClientFile:   client.GetConfigFile(s),\n\t\tLogDirectory: filelocation.AppUserLogDir(s),\n\t\tConfig:       client.GetConfig(s),\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/context.go",
    "content": "package trafficmgr\n\nimport (\n\t\"context\"\n)\n\ntype sessionKey struct{}\n\nfunc withSession(ctx context.Context, session *session) context.Context {\n\treturn context.WithValue(ctx, sessionKey{}, session)\n}\n\nfunc getSession(ctx context.Context) *session {\n\tif s, ok := ctx.Value(sessionKey{}).(*session); ok {\n\t\treturn s\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/gather_logs.go",
    "content": "package trafficmgr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\n\tcore \"k8s.io/api/core/v1\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\ttyped \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentmap\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/filelocation\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\n// getPodLog obtains the log and optionally the YAML for a given pod and stores it in\n// a file named <POD NAME>.<POD NAMESPACE>.log (and .yaml, if applicable) under the\n// given exportDir directory. An entry with the relative filename as a key is created\n// in the result map. The entry will either contain the string \"ok\" or an error when\n// the log or yaml for some reason could not be written to the file.\nfunc getPodLog(ctx context.Context, exportDir string, result *sync.Map, podsAPI typed.PodInterface, pod *core.Pod, container string, podYAML, agent bool) {\n\tif !agentmap.IsPodRunning(pod) || agent && agentmap.AgentContainer(pod) == nil {\n\t\treturn\n\t}\n\tpodLog := pod.Name + \".\" + pod.Namespace + \".log\"\n\treq := podsAPI.GetLogs(pod.Name, &core.PodLogOptions{Container: container})\n\tlogStream, err := req.Stream(ctx)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"failed to get log for %s.%s: %w\", pod.Name, pod.Namespace, err)\n\t\tclog.Error(ctx, err)\n\t\tresult.Store(podLog, err.Error())\n\t\treturn\n\t}\n\tdefer logStream.Close()\n\n\tf, err := os.Create(filepath.Join(exportDir, podLog))\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\tresult.Store(podLog, err.Error())\n\t\treturn\n\t}\n\tdefer f.Close()\n\n\tif _, err = io.Copy(f, logStream); err != nil {\n\t\terr = fmt.Errorf(\"failed writing log to buffer: %w\", err)\n\t\tclog.Error(ctx, err)\n\t\tresult.Store(podLog, err.Error())\n\t\treturn\n\t}\n\tresult.Store(podLog, \"ok\")\n\n\t// Get the pod yaml if the user asked for it\n\tif podYAML {\n\t\tvar b []byte\n\t\tpodYaml := pod.Name + \".\" + pod.Namespace + \".yaml\"\n\t\tif b, err = yaml.Marshal(pod); err != nil {\n\t\t\terr = fmt.Errorf(\"failed marshaling pod yaml: %w\", err)\n\t\t\tclog.Error(ctx, err)\n\t\t\tresult.Store(podYaml, err.Error())\n\t\t\treturn\n\t\t}\n\t\tif err = os.WriteFile(filepath.Join(exportDir, podYaml), b, 0o666); err != nil {\n\t\t\tresult.Store(podYaml, err.Error())\n\t\t\treturn\n\t\t}\n\t\tresult.Store(podYaml, \"ok\")\n\t}\n}\n\nfunc (s *session) foreachAgentPod(fn func(typed.PodInterface, *core.Pod), filter func(*core.Pod) bool) error {\n\thasContainer := func(pod *core.Pod) bool {\n\t\tif filter == nil || filter(pod) {\n\t\t\tcns := pod.Spec.Containers\n\t\t\tfor c := range cns {\n\t\t\t\tif cns[c].Name == agentconfig.ContainerName {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n\n\tcoreAPI := k8sapi.GetK8sInterface(s).CoreV1()\n\tfor _, ns := range s.GetCurrentNamespaces(true) {\n\t\tpodsAPI := coreAPI.Pods(ns)\n\t\tpodList, err := podsAPI.List(s, meta.ListOptions{})\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tpods := podList.Items\n\t\tpodsWithContainer := make([]*core.Pod, 0, len(pods))\n\t\tfor i := range pods {\n\t\t\tpod := &pods[i]\n\t\t\tif hasContainer(pod) {\n\t\t\t\tpodsWithContainer = append(podsWithContainer, pod)\n\t\t\t}\n\t\t}\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(len(podsWithContainer))\n\t\tfor _, pod := range podsWithContainer {\n\t\t\tgo func(pod *core.Pod) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tfn(podsAPI, pod)\n\t\t\t}(pod)\n\t\t}\n\t\twg.Wait()\n\t}\n\n\treturn nil\n}\n\n// GatherLogs acquires the logs for the traffic-manager and/or traffic-agents specified by the\n// connector.LogsRequest and returns them to the caller.\nfunc (s *session) GatherLogs(ctx context.Context, request *connector.LogsRequest) (*connector.LogsResponse, error) {\n\texportDir := filepath.Join(filelocation.AppUserCacheDir(ctx), request.ExportDir)\n\tcoreAPI := k8sapi.GetK8sInterface(ctx).CoreV1()\n\tresp := &connector.LogsResponse{}\n\tresult := sync.Map{}\n\n\tif !strings.EqualFold(request.Agents, \"none\") {\n\t\terr := s.foreachAgentPod(func(podsAPI typed.PodInterface, pod *core.Pod) {\n\t\t\tpodAndNs := fmt.Sprintf(\"%s.%s\", pod.Name, pod.Namespace)\n\t\t\tclog.Debugf(ctx, \"gathering logs for %s, yaml = %t\", podAndNs, request.GetPodYaml)\n\t\t\tgetPodLog(ctx, exportDir, &result, podsAPI, pod, agentconfig.ContainerName, request.GetPodYaml, true)\n\t\t}, func(pod *core.Pod) bool {\n\t\t\treturn strings.EqualFold(request.Agents, \"all\") || strings.Contains(pod.Name, request.Agents)\n\t\t})\n\t\tif err != nil {\n\t\t\tresp.Error = err.Error()\n\t\t\treturn resp, nil\n\t\t}\n\t}\n\n\t// We want to get the traffic-manager log *last* so that if we generate\n\t// any errors in the traffic-manager getting the traffic-agent pods, we\n\t// want those logs to appear in what we export\n\tif request.TrafficManager {\n\t\tns := k8s.GetManagerNamespace(ctx)\n\t\tpodsAPI := coreAPI.Pods(ns)\n\t\tselector := labels.SelectorFromSet(labels.Set{\n\t\t\t\"app\":          agentconfig.ManagerAppName,\n\t\t\t\"telepresence\": \"manager\",\n\t\t})\n\t\tpodList, err := podsAPI.List(ctx, meta.ListOptions{LabelSelector: selector.String()})\n\t\tswitch {\n\t\tcase err != nil:\n\t\t\terr = fmt.Errorf(\"failed to gather logs for traffic manager in namespace %s: %w\", ns, err)\n\t\t\tclog.Error(ctx, err)\n\t\t\tresp.Error = err.Error()\n\t\tcase len(podList.Items) == 1:\n\t\t\tpod := &podList.Items[0]\n\t\t\tpodAndNs := fmt.Sprintf(\"%s.%s\", pod.Name, ns)\n\t\t\tclog.Debugf(ctx, \"gathering logs for %s, yaml = %t\", podAndNs, request.GetPodYaml)\n\t\t\tgetPodLog(ctx, exportDir, &result, podsAPI, pod, agentconfig.ManagerAppName, request.GetPodYaml, false)\n\t\tcase len(podList.Items) > 1:\n\t\t\terr = fmt.Errorf(\"multiple traffic managers found in namespace %s using selector %s\", ns, selector.String())\n\t\t\tclog.Error(ctx, err)\n\t\t\tresp.Error = err.Error()\n\t\tdefault:\n\t\t\terr := fmt.Errorf(\"no traffic manager found in namespace %s using selector %s\", ns, selector.String())\n\t\t\tclog.Error(ctx, err)\n\t\t\tresp.Error = err.Error()\n\t\t}\n\t}\n\tpi := make(map[string]string)\n\tresult.Range(func(k, v any) bool {\n\t\tpi[k.(string)] = v.(string)\n\t\treturn true\n\t})\n\tresp.PodInfo = pi\n\treturn resp, nil\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/ingest.go",
    "content": "package trafficmgr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/remotefs\"\n)\n\ntype ingestKey struct {\n\tworkload  string\n\tcontainer string\n}\n\nfunc (ik ingestKey) String() string {\n\treturn fmt.Sprintf(\"%s[%s]\", ik.workload, ik.container)\n}\n\ntype ingest struct {\n\t*manager.AgentInfo\n\tingestKey\n\twg               sync.WaitGroup\n\tctx              context.Context\n\tcancel           context.CancelFunc\n\tlocalMountPoint  string\n\tlocalMountPort   int32\n\tlocalPorts       []string\n\thandlerContainer string\n\tpid              int\n\tmounter          remotefs.Mounter\n}\n\nfunc (ig *ingest) podAccess(rd daemon.DaemonClient) *podAccess {\n\tni := ig.Containers[ig.container]\n\tpa := &podAccess{\n\t\tctx:              ig.ctx,\n\t\tlocalPorts:       ig.localPorts,\n\t\tworkload:         ig.workload,\n\t\tcontainer:        ig.container,\n\t\tpodIP:            ig.PodIp,\n\t\tsftpPort:         ig.SftpPort,\n\t\tftpPort:          ig.FtpPort,\n\t\tmountPoint:       ni.MountPoint,\n\t\tclientMountPoint: ig.localMountPoint,\n\t\tlocalMountPort:   ig.localMountPort,\n\t\tmounter:          &ig.mounter,\n\t\treadOnly:         true,\n\t\twg:               &ig.wg,\n\t}\n\tif err := pa.ensureAccess(ig.ctx, rd); err != nil {\n\t\tclog.Error(ig.ctx, err)\n\t}\n\treturn pa\n}\n\nfunc (ig *ingest) response() *rpc.IngestInfo {\n\tcn := ig.Containers[ig.container]\n\tii := &rpc.IngestInfo{\n\t\tWorkload:         ig.workload,\n\t\tWorkloadKind:     ig.Kind,\n\t\tContainer:        ig.container,\n\t\tPodIp:            ig.PodIp,\n\t\tSftpPort:         ig.SftpPort,\n\t\tFtpPort:          ig.FtpPort,\n\t\tMountPoint:       cn.MountPoint,\n\t\tMounts:           cn.Mounts,\n\t\tClientMountPoint: ig.localMountPoint,\n\t\tEnvironment:      cn.Environment,\n\t}\n\tif ig.handlerContainer != \"\" {\n\t\tif ii.Environment == nil {\n\t\t\tii.Environment = make(map[string]string, 1)\n\t\t}\n\t\tii.Environment[\"TELEPRESENCE_HANDLER_CONTAINER_NAME\"] = ig.handlerContainer\n\t}\n\treturn ii\n}\n\nfunc (s *session) getSingleContainerName(ai *manager.AgentInfo) (name string, err error) {\n\tif err = s.validateAgentForIngest(ai); err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(ai.Containers) > 1 {\n\t\treturn \"\", status.Error(codes.NotFound, fmt.Sprintf(\"workload %s has multiple containers. Please specify which one to use\", ai.Name))\n\t}\n\tfor name = range ai.Containers {\n\t}\n\treturn name, err\n}\n\nfunc (s *session) validateAgentForIngest(ai *manager.AgentInfo) error {\n\tif len(ai.Containers) == 0 {\n\t\treturn status.Error(codes.Unimplemented, fmt.Sprintf(\"traffic-manager %s has no support for ingest\", s.managerVersion))\n\t}\n\treturn nil\n}\n\nfunc (s *session) getCurrentAgent(name string) *manager.AgentInfo {\n\tfor _, ai := range s.getCurrentAgents() {\n\t\tif ai.Name == name {\n\t\t\treturn ai\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *session) Ingest(ctx context.Context, rq *rpc.IngestRequest) (ir *rpc.IngestInfo, err error) {\n\tid := rq.Identifier\n\tik := ingestKey{\n\t\tworkload:  id.WorkloadName,\n\t\tcontainer: id.ContainerName,\n\t}\n\tai := s.getCurrentAgent(ik.workload)\n\n\tif ai != nil {\n\t\tif ik.container == \"\" {\n\t\t\tik.container, err = s.getSingleContainerName(ai)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tif ig, loaded := s.currentIngests.Load(ik); loaded {\n\t\t\treturn ig.response(), nil\n\t\t}\n\t}\n\n\terr = s.ensureNoMountConflict(rq.MountPoint, rq.LocalMountPort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif ai == nil {\n\t\tvar as *manager.AgentInfoSnapshot\n\t\ttimeoutCtx, cancel := client.GetConfig(s).Timeouts().TimeoutContext(s, client.TimeoutIntercept)\n\t\tdefer cancel()\n\t\tas, err = s.ManagerClient().EnsureAgent(timeoutCtx, &manager.EnsureAgentRequest{Session: s.sessionInfo, Name: ik.workload})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tai = as.Agents[0]\n\t}\n\tif err = s.validateAgentForIngest(ai); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif ik.container == \"\" {\n\t\tik.container, err = s.getSingleContainerName(ai)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else if _, ok := ai.Containers[ik.container]; !ok {\n\t\treturn nil, fmt.Errorf(\"workload %s has no container named %s\", ik.workload, ik.container)\n\t}\n\n\terr = s.translateContainerEnv(ctx, ai, ik.container)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tig, loaded := s.currentIngests.LoadOrCompute(ik, func() (*ingest, bool) {\n\t\tctx, cancel := context.WithCancel(s)\n\t\tcancelIngest := func() {\n\t\t\ts.currentIngests.Delete(ik)\n\t\t\tclog.Debugf(ctx, \"Cancelling ingest %s\", ik)\n\t\t\tcancel()\n\t\t\ts.ingestTracker.cancelContainer(ik.workload, ik.container)\n\t\t}\n\t\treturn &ingest{\n\t\t\tingestKey:       ik,\n\t\t\tAgentInfo:       ai,\n\t\t\tctx:             ctx,\n\t\t\tcancel:          cancelIngest,\n\t\t\tlocalMountPoint: rq.MountPoint,\n\t\t\tlocalMountPort:  rq.LocalMountPort,\n\t\t\tlocalPorts:      rq.LocalPorts,\n\t\t}, false\n\t})\n\tif !loaded {\n\t\ts.ingestTracker.initialStart(ig.podAccess(s.rootDaemon))\n\t}\n\treturn ig.response(), nil\n}\n\nfunc (s *session) translateContainerEnv(ctx context.Context, ai *manager.AgentInfo, container string) error {\n\tcn, ok := ai.Containers[container]\n\tif !ok {\n\t\treturn fmt.Errorf(\"workload %s has no container named %s\", ai.Name, container)\n\t}\n\treturn s.WithRootClient(ctx, func(ctx context.Context, rd daemon.DaemonClient) (err error) {\n\t\tenv, err := s.rootDaemon.TranslateEnvIPs(s, &daemon.Environment{Env: cn.Environment})\n\t\tif err == nil {\n\t\t\tcn.Environment = env.Env\n\t\t}\n\t\treturn err\n\t})\n}\n\nfunc (s *session) getCurrentIngests() []*rpc.IngestInfo {\n\tingests := make([]*rpc.IngestInfo, 0, s.currentIngests.Size())\n\ts.currentIngests.Range(func(key ingestKey, ig *ingest) bool {\n\t\tingests = append(ingests, ig.response())\n\t\treturn true\n\t})\n\treturn ingests\n}\n\nfunc (s *session) findIngest(workloadName, containerName string) (ig *ingest, err error) {\n\tif containerName == \"\" {\n\t\t// Valid if there's only one ingest for the given workload.\n\t\tvar foundIngest *ingest\n\t\tvar err error\n\n\t\ts.currentIngests.Range(func(key ingestKey, value *ingest) bool {\n\t\t\tif key.workload == workloadName {\n\t\t\t\tif foundIngest != nil {\n\t\t\t\t\terr = status.Error(codes.NotFound, fmt.Sprintf(\"workload %s has multiple ingests. Please specify which one to use\", workloadName))\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tfoundIngest = value\n\t\t\t\tcontainerName = key.container\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif foundIngest == nil {\n\t\t\treturn nil, status.Error(codes.NotFound, fmt.Sprintf(\"no ingest found for workload %s\", workloadName))\n\t\t}\n\n\t\treturn foundIngest, nil\n\t}\n\n\tik := ingestKey{\n\t\tworkload:  workloadName,\n\t\tcontainer: containerName,\n\t}\n\tif ig, ok := s.currentIngests.Load(ik); ok {\n\t\treturn ig, nil\n\t}\n\treturn nil, status.Error(codes.NotFound, fmt.Sprintf(\"ingest %s doesn't exist\", ik))\n}\n\nfunc (s *session) getIngest(rq *rpc.IngestIdentifier) (ig *ingest, err error) {\n\treturn s.findIngest(rq.WorkloadName, rq.ContainerName)\n}\n\nfunc (s *session) GetIngest(rq *rpc.IngestIdentifier) (ii *rpc.IngestInfo, err error) {\n\tig, err := s.getIngest(rq)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ig.response(), nil\n}\n\nfunc (s *session) LeaveIngest(rq *rpc.IngestIdentifier) (ii *rpc.IngestInfo, err error) {\n\tig, err := s.getIngest(rq)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.stopHandler(fmt.Sprintf(\"%s/%s\", ig.workload, ig.container), ig.handlerContainer, ig.pid)\n\tig.cancel()\n\tig.wg.Wait()\n\treturn ig.response(), nil\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/ingest_test.go",
    "content": "package trafficmgr\n\nimport (\n\t\"testing\"\n\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n)\n\nfunc TestSession_findIngest(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tsetup         func(*session)\n\t\tworkloadName  string\n\t\tcontainerName string\n\t\twantErr       bool\n\t\twantErrCode   codes.Code\n\t\twantErrMsg    string\n\t\tvalidate      func(*testing.T, *ingest)\n\t}{\n\t\t{\n\t\t\tname:          \"finds ingest with workload and container specified\",\n\t\t\tworkloadName:  \"my-workload\",\n\t\t\tcontainerName: \"my-container\",\n\t\t\tsetup: func(s *session) {\n\t\t\t\tig := &ingest{\n\t\t\t\t\tingestKey: ingestKey{\n\t\t\t\t\t\tworkload:  \"my-workload\",\n\t\t\t\t\t\tcontainer: \"my-container\",\n\t\t\t\t\t},\n\t\t\t\t\tAgentInfo: &manager.AgentInfo{\n\t\t\t\t\t\tName: \"my-workload\",\n\t\t\t\t\t\tContainers: map[string]*manager.AgentInfo_ContainerInfo{\n\t\t\t\t\t\t\t\"my-container\": {},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\ts.currentIngests.Store(ig.ingestKey, ig)\n\t\t\t},\n\t\t\twantErr: false,\n\t\t\tvalidate: func(t *testing.T, ig *ingest) {\n\t\t\t\tassert.Equal(t, \"my-workload\", ig.workload)\n\t\t\t\tassert.Equal(t, \"my-container\", ig.container)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:          \"finds ingest with only workload specified (single container)\",\n\t\t\tworkloadName:  \"my-workload\",\n\t\t\tcontainerName: \"\",\n\t\t\tsetup: func(s *session) {\n\t\t\t\tig := &ingest{\n\t\t\t\t\tingestKey: ingestKey{\n\t\t\t\t\t\tworkload:  \"my-workload\",\n\t\t\t\t\t\tcontainer: \"my-container\",\n\t\t\t\t\t},\n\t\t\t\t\tAgentInfo: &manager.AgentInfo{\n\t\t\t\t\t\tName: \"my-workload\",\n\t\t\t\t\t\tContainers: map[string]*manager.AgentInfo_ContainerInfo{\n\t\t\t\t\t\t\t\"my-container\": {},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\ts.currentIngests.Store(ig.ingestKey, ig)\n\t\t\t},\n\t\t\twantErr: false,\n\t\t\tvalidate: func(t *testing.T, ig *ingest) {\n\t\t\t\tassert.Equal(t, \"my-workload\", ig.workload)\n\t\t\t\tassert.Equal(t, \"my-container\", ig.container)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:          \"errors when workload has multiple containers and no container specified\",\n\t\t\tworkloadName:  \"my-workload\",\n\t\t\tcontainerName: \"\",\n\t\t\tsetup: func(s *session) {\n\t\t\t\tig1 := &ingest{\n\t\t\t\t\tingestKey: ingestKey{\n\t\t\t\t\t\tworkload:  \"my-workload\",\n\t\t\t\t\t\tcontainer: \"container-1\",\n\t\t\t\t\t},\n\t\t\t\t\tAgentInfo: &manager.AgentInfo{\n\t\t\t\t\t\tName: \"my-workload\",\n\t\t\t\t\t\tContainers: map[string]*manager.AgentInfo_ContainerInfo{\n\t\t\t\t\t\t\t\"container-1\": {},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\tig2 := &ingest{\n\t\t\t\t\tingestKey: ingestKey{\n\t\t\t\t\t\tworkload:  \"my-workload\",\n\t\t\t\t\t\tcontainer: \"container-2\",\n\t\t\t\t\t},\n\t\t\t\t\tAgentInfo: &manager.AgentInfo{\n\t\t\t\t\t\tName: \"my-workload\",\n\t\t\t\t\t\tContainers: map[string]*manager.AgentInfo_ContainerInfo{\n\t\t\t\t\t\t\t\"container-2\": {},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t\ts.currentIngests.Store(ig1.ingestKey, ig1)\n\t\t\t\ts.currentIngests.Store(ig2.ingestKey, ig2)\n\t\t\t},\n\t\t\twantErr:     true,\n\t\t\twantErrCode: codes.NotFound,\n\t\t\twantErrMsg:  \"workload my-workload has multiple ingests\",\n\t\t},\n\t\t{\n\t\t\tname:          \"errors when ingest doesn't exist\",\n\t\t\tworkloadName:  \"nonexistent\",\n\t\t\tcontainerName: \"container\",\n\t\t\tsetup:         func(s *session) {},\n\t\t\twantErr:       true,\n\t\t\twantErrCode:   codes.NotFound,\n\t\t\twantErrMsg:    \"doesn't exist\",\n\t\t},\n\t\t{\n\t\t\tname:          \"errors when no ingest found for workload\",\n\t\t\tworkloadName:  \"nonexistent\",\n\t\t\tcontainerName: \"\",\n\t\t\tsetup:         func(s *session) {},\n\t\t\twantErr:       true,\n\t\t\twantErrCode:   codes.NotFound,\n\t\t\twantErrMsg:    \"no ingest found for workload\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := &session{\n\t\t\t\tcurrentIngests: xsync.NewMap[ingestKey, *ingest](),\n\t\t\t}\n\n\t\t\tif tt.setup != nil {\n\t\t\t\ttt.setup(s)\n\t\t\t}\n\n\t\t\tig, err := s.findIngest(tt.workloadName, tt.containerName)\n\n\t\t\tif tt.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\tst, ok := status.FromError(err)\n\t\t\t\trequire.True(t, ok, \"error should be a gRPC status error\")\n\t\t\t\tassert.Equal(t, tt.wantErrCode, st.Code())\n\t\t\t\tassert.Contains(t, st.Message(), tt.wantErrMsg)\n\t\t\t\tassert.Nil(t, ig)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotNil(t, ig)\n\t\t\t\tif tt.validate != nil {\n\t\t\t\t\ttt.validate(t, ig)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/intercept.go",
    "content": "package trafficmgr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tcore \"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/docker\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/remotefs\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/userd\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/watcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/matcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/restapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// intercept tracks the life-cycle of an intercept, dictated by the intercepts\n// arrival and departure in the watchInterceptsLoop.\ntype intercept struct {\n\tsync.Mutex\n\t*manager.InterceptInfo\n\n\t// ctx is a context cancelled by the cancel attribute. It must be used by\n\t// services that should be cancelled when the intercept ends\n\tctx context.Context\n\n\t// cancel is called when the intercept is no longer present\n\tcancel context.CancelFunc\n\n\t// wg is the group to wait for after a call to cancel\n\twg sync.WaitGroup\n\n\t// pid of intercept handler for an intercept. This entry will only be present when\n\t// the telepresence intercept command spawns a new command. The int value reflects\n\t// the pid of that new command.\n\tpid int\n\n\t// handlerContainer is the name or ID of the container that the intercept handler is\n\t// running in, when it runs in Docker. As with pid, this entry will only be present when\n\t// the telepresence intercept command spawns a new command using --docker-run or\n\t// --docker-build\n\thandlerContainer string\n\n\t// The mounter of the remote file system.\n\tremotefs.Mounter\n\n\t// Use bridged ftp/sftp mount through this local port\n\tlocalMountPort int32\n\n\t// Mount read-only\n\treadOnly bool\n\n\t// finalRemovalDone is closed when the traffic-manager sends a snapshot that no longer contains\n\t// this intercept.\n\tfinalRemovalDone chan struct{}\n}\n\n// interceptResult is what gets written to the awaitIntercept's waitCh channel when the\n// awaited intercept arrives.\ntype interceptResult struct {\n\tintercept  *intercept\n\tmountsDone <-chan struct{}\n\terr        error\n}\n\n// awaitIntercept is what the traffic-manager is using to notify the watchInterceptsLoop\n// about an expected intercept arrival.\ntype awaitIntercept struct {\n\t// mountPoint is the mount point assigned to the InterceptInfo's ClientMountPoint when\n\t// it arrives from the traffic-manager.\n\tmountPoint string\n\n\t// mountPort is optional and indicates that a TCP bridge should be established, allowing\n\t// the mount to take place in a host\n\tmountPort int32\n\n\treadOnly bool\n\twaitCh   chan<- interceptResult\n}\n\nfunc (ic *intercept) localPorts() []string {\n\t// Older versions use ii.extraPorts (TCP only), newer versions use ii.localPorts.\n\tps := ic.Spec.LocalPorts\n\tif len(ps) == 0 {\n\t\tfor _, ep := range ic.Spec.ExtraPorts {\n\t\t\tps = append(ps, strconv.Itoa(int(ep)))\n\t\t}\n\t\tic.Spec.LocalPorts = ps\n\t}\n\treturn ps\n}\n\nfunc (ic *intercept) podAccess() *podAccess {\n\treturn &podAccess{\n\t\tctx:              ic.ctx,\n\t\tlocalPorts:       ic.localPorts(),\n\t\tworkload:         ic.Spec.Agent,\n\t\tpodIP:            ic.PodIp,\n\t\tcontainer:        ic.Spec.ContainerName,\n\t\tsftpPort:         ic.SftpPort,\n\t\tftpPort:          ic.FtpPort,\n\t\tmountPoint:       ic.MountPoint,\n\t\tclientMountPoint: ic.ClientMountPoint,\n\t\tlocalMountPort:   ic.localMountPort,\n\t\treadOnly:         ic.readOnly,\n\t\tmounter:          &ic.Mounter,\n\t\twg:               &ic.wg,\n\t}\n}\n\nfunc (s *session) watchInterceptsHandler(ctx context.Context) error {\n\treturn runWithRetry(ctx, s.watchInterceptsLoop)\n}\n\nfunc (s *session) watchInterceptsLoop(ctx context.Context) error {\n\tpat := newPodAccessTracker()\n\tsnapMap := make(map[string]*manager.InterceptInfo)\n\terr := watcher.WatchWithRetry(ctx, \"WatchInterceptsDelta\", client.GetConfig(ctx).Grpc().WatchRetryInterval,\n\t\tfunc(ctx context.Context) (grpc.ServerStreamingClient[manager.InterceptInfoDelta], error) {\n\t\t\treturn s.ManagerClient().WatchInterceptsDelta(s, s.SessionInfo())\n\t\t},\n\t\tfunc(delta *manager.InterceptInfoDelta) error {\n\t\t\tmaps.DeltaUpdate(snapMap, delta.Upserts, delta.Removals)\n\t\t\ts.handleInterceptSnapshot(pat, maps.Values(snapMap))\n\t\t\treturn nil\n\t\t}, func() error {\n\t\t\tclear(snapMap)\n\t\t\treturn s.reconnectManager()\n\t\t})\n\tif err != nil && status.Code(err) == codes.Unimplemented {\n\t\t// Fall back to streaming all intercepts if the traffic manager doesn't support delta updates.'\n\t\tclog.Warnf(ctx, \"WatchInterceptsDelta is not implemented by the traffic-manager, falling back to WatchIntercepts and full snapshots\")\n\t\terr = watcher.WatchWithRetry(ctx, \"WatchIntercepts\", client.GetConfig(ctx).Grpc().WatchRetryInterval,\n\t\t\tfunc(ctx context.Context) (grpc.ServerStreamingClient[manager.InterceptInfoSnapshot], error) {\n\t\t\t\treturn s.ManagerClient().WatchIntercepts(s, s.SessionInfo())\n\t\t\t},\n\t\t\tfunc(snapshot *manager.InterceptInfoSnapshot) error {\n\t\t\t\ts.handleInterceptSnapshot(pat, snapshot.Intercepts)\n\t\t\t\treturn nil\n\t\t\t}, s.reconnectManager)\n\t}\n\t// Handle as if we had an empty snapshot. This will ensure that port forwards and volume mounts are cancelled correctly.\n\ts.handleInterceptSnapshot(pat, nil)\n\treturn err\n}\n\nfunc (s *session) handleInterceptSnapshot(pat *podAccessTracker, intercepts []*manager.InterceptInfo) {\n\ts.setCurrentIntercepts(intercepts)\n\tpat.initSnapshot()\n\n\tfor _, ii := range intercepts {\n\t\tif ii.Disposition == manager.InterceptDispositionType_WAITING {\n\t\t\tcontinue\n\t\t}\n\n\t\ts.currentInterceptsLock.Lock()\n\t\tic := s.currentIntercepts[ii.Id]\n\t\taw := s.interceptWaiters[ii.Spec.Name]\n\t\tif aw != nil {\n\t\t\tdelete(s.interceptWaiters, ii.Spec.Name)\n\t\t}\n\t\ts.currentInterceptsLock.Unlock()\n\n\t\tpa := ic.podAccess()\n\t\tvar err error\n\t\tif ii.Disposition == manager.InterceptDispositionType_ACTIVE {\n\t\t\tns := ii.Spec.Namespace\n\t\t\tif s.Namespace != ns {\n\t\t\t\terr = errcat.User.Newf(\"active intercepts in both namespace %s and %s\", ns, s.Namespace)\n\t\t\t} else {\n\t\t\t\terr = pa.ensureAccess(ic.ctx, s.rootDaemon)\n\t\t\t}\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"intercept in error state %v: %v\", ii.Disposition, ii.Message)\n\t\t}\n\n\t\t// Notify waiters for active intercepts\n\t\tif aw != nil {\n\t\t\tclog.Debugf(s, \"wait status: intercept id=%q is no longer WAITING; is now %v\", ii.Id, ii.Disposition)\n\t\t\tir := interceptResult{\n\t\t\t\tintercept: ic,\n\t\t\t\terr:       err,\n\t\t\t}\n\t\t\tif err == nil {\n\t\t\t\tir.mountsDone = pat.getOrCreateMountsDone(pa)\n\t\t\t} else {\n\t\t\t\tmd := make(chan struct{})\n\t\t\t\tclose(md)\n\t\t\t\tir.mountsDone = md\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase aw.waitCh <- ir:\n\t\t\t\tif err != nil {\n\t\t\t\t\t// Error logged by receiver\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\t// Channel was closed\n\t\t\t\tclog.Debugf(s, \"unable to propagate intercept id=%q\", ii.Id)\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tclog.Error(s, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif s.isPodDaemon {\n\t\t\t// disable mount point logic\n\t\t\tpa.ftpPort = 0\n\t\t\tpa.sftpPort = 0\n\t\t}\n\t\tpat.start(pa)\n\t}\n\tpat.cancelUnwanted(s)\n}\n\n// getCurrentIntercepts returns a copy of the current intercept snapshot. This snapshot does\n// not include any local-only intercepts.\nfunc (s *session) getCurrentIntercepts() []*intercept {\n\t// Copy the current snapshot\n\ts.currentInterceptsLock.Lock()\n\tintercepts := maps.ToSortedSlice(s.currentIntercepts)\n\ts.currentInterceptsLock.Unlock()\n\treturn intercepts\n}\n\n// getCurrentInterceptInfos returns the InterceptInfos of the current intercept snapshot.\nfunc (s *session) getCurrentInterceptInfos() []*manager.InterceptInfo {\n\t// Copy the current snapshot\n\tics := s.getCurrentIntercepts()\n\tifs := make([]*manager.InterceptInfo, len(ics))\n\tfor idx, ic := range ics {\n\t\tifs[idx] = ic.InterceptInfo\n\t}\n\treturn ifs\n}\n\nfunc (s *session) setCurrentIntercepts(iis []*manager.InterceptInfo) {\n\ts.currentInterceptsLock.Lock()\n\tintercepts := make(map[string]*intercept, len(iis))\n\tsb := strings.Builder{}\n\tsb.WriteByte('[')\n\tfor i, ii := range iis {\n\t\tic, ok := s.currentIntercepts[ii.Id]\n\t\tif ok {\n\t\t\t// retain ClientMountPoint, it's assigned in the client and never passed from the traffic-manager\n\t\t\tii.ClientMountPoint = ic.ClientMountPoint\n\t\t\tic.InterceptInfo = ii\n\t\t} else {\n\t\t\tic = &intercept{InterceptInfo: ii, finalRemovalDone: make(chan struct{})}\n\t\t\tic.ctx, ic.cancel = context.WithCancel(s)\n\t\t\tclog.Debugf(s, \"Received new intercept %s\", ic.Spec.Name)\n\t\t\tif aw, ok := s.interceptWaiters[ii.Spec.Name]; ok {\n\t\t\t\tic.ClientMountPoint = aw.mountPoint\n\t\t\t\tic.localMountPort = aw.mountPort\n\t\t\t\tic.readOnly = aw.readOnly\n\t\t\t}\n\t\t}\n\t\tintercepts[ii.Id] = ic\n\t\tif i > 0 {\n\t\t\tsb.WriteByte(',')\n\t\t}\n\t\tsb.WriteString(ii.Spec.Name)\n\t\tsb.WriteByte('=')\n\t\tsb.WriteString(ii.PodIp)\n\t}\n\tsb.WriteByte(']')\n\tclog.Debugf(s, \"setCurrentIntercepts(%s)\", sb.String())\n\n\t// Cancel those that no longer exists\n\tvar removed []*intercept\n\tfor id, ic := range s.currentIntercepts {\n\t\tif _, ok := intercepts[id]; !ok {\n\t\t\tremoved = append(removed, ic)\n\t\t}\n\t}\n\ts.currentIntercepts = intercepts\n\ts.reconcileAPIServers()\n\ts.currentInterceptsLock.Unlock()\n\n\tfor _, ic := range removed {\n\t\tclog.Debugf(s, \"Cancelling context for intercept %s\", ic.Spec.Name)\n\t\tic.cancel()\n\t\tclose(ic.finalRemovalDone)\n\t}\n}\n\ntype interceptInfo struct {\n\t// Information provided by the traffic manager as response to the PrepareIntercept call\n\tpreparedIntercept *manager.PreparedIntercept\n}\n\nfunc (s *interceptInfo) PortIdentifier() (types.PortIdentifier, error) {\n\tvar spi string\n\tif s.preparedIntercept.ServicePortName == \"\" {\n\t\tspi = strconv.Itoa(int(s.preparedIntercept.ServicePort))\n\t} else {\n\t\tspi = s.preparedIntercept.ServicePortName\n\t}\n\treturn types.NewPortIdentifier(types.FromK8sProtocol(core.Protocol(s.preparedIntercept.Protocol)), spi)\n}\n\nfunc (s *interceptInfo) PreparedIntercept() *manager.PreparedIntercept {\n\treturn s.preparedIntercept\n}\n\nfunc (s *session) ensureNoInterceptConflict(ir *rpc.CreateInterceptRequest) error {\n\terr := s.ensureNoMountConflict(ir.MountPoint, ir.LocalMountPort)\n\tif err != nil {\n\t\treturn err\n\t}\n\ts.currentInterceptsLock.Lock()\n\tdefer s.currentInterceptsLock.Unlock()\n\tspec := ir.Spec\n\tfor _, iCept := range s.currentIntercepts {\n\t\tif iCept.Spec.Name == spec.Name {\n\t\t\treturn status.Errorf(codes.AlreadyExists, \"intercept with name %q already exists\", spec.Name)\n\t\t}\n\t}\n\treturn nil\n}\n\n// allBusyLocalPorts returns the sum of all ports that the intercept forwards to and all ports\n// that are forwarded from.\nfunc allBusyLocalPorts(targetHost netip.Addr, spec *manager.InterceptSpec) ([]types.AddrPortProto, error) {\n\ttargetPort := spec.TargetPort\n\tif targetPort == 0 {\n\t\ttargetPort = spec.ContainerPort\n\t}\n\tproto, err := types.ParseProto(spec.Protocol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tports := make([]types.AddrPortProto, 0, len(spec.LocalPorts)+len(spec.PodPorts)+1)\n\tports = append(ports, types.AddrPortProto{\n\t\tAddrPort: netip.AddrPortFrom(targetHost, uint16(targetPort)),\n\t\tProto:    proto,\n\t})\n\tfor _, lp := range spec.LocalPorts {\n\t\tpp, _ := types.ParsePortAndProto(lp)\n\t\tports = append(ports, types.AddrPortProto{\n\t\t\tAddrPort: netip.AddrPortFrom(targetHost, pp.Port),\n\t\t\tProto:    pp.Proto,\n\t\t})\n\t}\n\tfor _, ps := range spec.PodPorts {\n\t\tpm := types.PortMapping(ps)\n\t\tpp := pm.ToAsNumeric()\n\t\tports = append(ports, types.AddrPortProto{\n\t\t\tAddrPort: netip.AddrPortFrom(targetHost, pp.Port),\n\t\t\tProto:    pp.Proto,\n\t\t})\n\t}\n\treturn ports, nil\n}\n\n// ensureUniqueLocalPorts returns the sum of all local ports that the intercept will forward to, and all\n// local ports that the client will forward from. Also ensures that there are no conflicts among those ports.\n// The cluster-side of the port mappings are not checked here because we rely on the PrepareIntercept\n// call to already have done that.\nfunc ensureUniqueLocalPorts(targetHost netip.Addr, spec *manager.InterceptSpec, pi *manager.PreparedIntercept) (map[types.AddrPortProto]struct{}, error) {\n\ttargetPort := spec.TargetPort\n\tif targetPort == 0 {\n\t\ttargetPort = pi.ContainerPort\n\t}\n\n\tproto, err := types.ParseProto(pi.Protocol)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tports := make(map[types.AddrPortProto]struct{}, len(spec.LocalPorts)+len(pi.PodPorts)+1)\n\tports[types.AddrPortProto{\n\t\tAddrPort: netip.AddrPortFrom(targetHost, uint16(targetPort)),\n\t\tProto:    proto,\n\t}] = struct{}{}\n\n\tfor _, lp := range spec.LocalPorts {\n\t\tpp, err := types.ParsePortAndProto(lp)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tap := types.AddrPortProto{\n\t\t\tAddrPort: netip.AddrPortFrom(targetHost, pp.Port),\n\t\t\tProto:    pp.Proto,\n\t\t}\n\t\tif _, ok := ports[ap]; ok {\n\t\t\treturn nil, fmt.Errorf(\"multiple use of port %s on %s\", &pp, spec.TargetHost)\n\t\t}\n\t\tports[ap] = struct{}{}\n\t}\n\tfor _, ps := range pi.PodPorts {\n\t\tpm := types.PortMapping(ps)\n\t\tif err := pm.Validate(); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpp := pm.ToAsNumeric()\n\t\tap := types.AddrPortProto{\n\t\t\tAddrPort: netip.AddrPortFrom(targetHost, pp.Port),\n\t\t\tProto:    pp.Proto,\n\t\t}\n\t\tif _, ok := ports[ap]; ok {\n\t\t\treturn nil, fmt.Errorf(\"multiple use of port %s on %s\", &ap, spec.TargetHost)\n\t\t}\n\t\tports[ap] = struct{}{}\n\t}\n\treturn ports, nil\n}\n\nfunc (s *session) ensureNoPortConflict(spec *manager.InterceptSpec, ir *manager.PreparedIntercept) error {\n\ttargetHost, err := netip.ParseAddr(spec.TargetHost)\n\tif err != nil {\n\t\treturn errcat.User.Errorf(err, \"invalid target host\")\n\t}\n\tports, err := ensureUniqueLocalPorts(targetHost, spec, ir)\n\tif err != nil {\n\t\treturn errcat.User.New(err)\n\t}\n\n\ts.currentInterceptsLock.Lock()\n\tdefer s.currentInterceptsLock.Unlock()\n\tfor _, ci := range s.currentIntercepts {\n\t\tciSpec := ci.Spec\n\t\ttargetHost, err = netip.ParseAddr(ciSpec.TargetHost)\n\t\tif err != nil {\n\t\t\treturn errcat.User.Errorf(err, \"invalid target host\")\n\t\t}\n\t\tbusyPorts, err := allBusyLocalPorts(targetHost, ciSpec)\n\t\tif err != nil {\n\t\t\treturn errcat.User.New(err)\n\t\t}\n\t\tfor _, blp := range busyPorts {\n\t\t\tif _, ok := ports[blp]; ok {\n\t\t\t\treturn &tpGrpc.StructuredError{\n\t\t\t\t\tMessage:  fmt.Sprintf(\"port %s is already in use by intercept %s\", blp, ciSpec.Name),\n\t\t\t\t\tCategory: errcat.User,\n\t\t\t\t\tCode:     codes.AlreadyExists,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *session) compareFinalizedManagerVersion(major, minor, patch uint64) int {\n\tmv := s.managerVersion\n\tn := mv.Major - major\n\tif n == 0 {\n\t\tif n = mv.Minor - minor; n == 0 {\n\t\t\tn = mv.Patch - patch\n\t\t}\n\t}\n\treturn int(n)\n}\n\n// CanIntercept checks if it is possible to create an intercept for the given request. The intercept can proceed\n// only if the returned rpc.InterceptResult is nil. The returned runtime.Object is either nil, indicating a local\n// intercept, or the workload for the intercept.\nfunc (s *session) CanIntercept(ctx context.Context, ir *rpc.CreateInterceptRequest) (userd.InterceptInfo, error) {\n\tspec := ir.Spec\n\tif spec.Namespace == \"\" {\n\t\tspec.Namespace = s.Namespace\n\t} else if s.Namespace != spec.Namespace {\n\t\treturn nil, errcat.User.Newf(\"attempt to intercept in namespace %q. Only the connected %q namespace can be intercepted\", spec.Namespace, s.Namespace)\n\t}\n\n\tif er := s.ensureNoInterceptConflict(ir); er != nil {\n\t\treturn nil, er\n\t}\n\n\tif spec.Wiretap && s.compareFinalizedManagerVersion(2, 23, 0) < 0 {\n\t\treturn nil, errcat.User.Newf(\"traffic-manager version %s has no support for wiretaps\", s.managerVersion)\n\t}\n\n\tif (spec.PortIdentifier == \"all\" || len(spec.PodPorts) > 0) && s.compareFinalizedManagerVersion(2, 22, 0) < 0 {\n\t\treturn nil, errcat.User.Newf(\"traffic-manager version %s has no support for multi-port intercepts\", s.managerVersion)\n\t}\n\n\t_, err := netip.ParseAddr(spec.TargetHost)\n\tif err != nil {\n\t\t// The targetHost is not a valid IP. Treat it as a name and create a synthetic IP for it.\n\t\trndIP, err := uuid.NewRandom()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to generate synthetic IP: %w\", err)\n\t\t}\n\t\ttargetIP := netip.AddrFrom16(rndIP)\n\t\tif s.syntheticIPs == nil {\n\t\t\ts.syntheticIPs = make(map[netip.Addr]string)\n\t\t}\n\t\tclog.Debugf(s, \"Replacing target host %s with synthetic IP %s\", spec.TargetHost, targetIP)\n\t\ts.syntheticIPs[targetIP] = spec.TargetHost\n\t\tspec.TargetHost = targetIP.String()\n\t}\n\n\tmgrIr := s.newCreateInterceptRequest(spec)\n\ttimeoutCtx, cancel := client.GetConfig(ctx).Timeouts().TimeoutContext(ctx, client.TimeoutIntercept)\n\tdefer cancel()\n\tvar pi *manager.PreparedIntercept\n\tfor retry := 0; retry < 2; retry++ {\n\t\tpi, err = s.ManagerClient().PrepareIntercept(timeoutCtx, mgrIr)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tif st, ok := status.FromError(err); ok {\n\t\t\tswitch st.Code() {\n\t\t\tcase codes.FailedPrecondition:\n\t\t\t\treturn nil, errcat.User.New(st.Message())\n\t\t\tcase codes.NotFound:\n\t\t\t\tif strings.HasPrefix(st.Message(), \"Client session \") {\n\t\t\t\t\t// The manager is not aware of this session. This can happen if the manager is restarted and\n\t\t\t\t\t// none of our watchers have yet detected and remedied the situation.\n\t\t\t\t\terr = s.reconnectManager()\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif pi.Error != \"\" {\n\t\treturn nil, errcat.Category(pi.ErrorCategory).New(pi.Error)\n\t}\n\tif er := s.ensureNoPortConflict(spec, pi); er != nil {\n\t\treturn nil, er\n\t}\n\n\tiInfo := &interceptInfo{preparedIntercept: pi}\n\treturn iInfo, nil\n}\n\nfunc (s *session) Resolve(addr netip.Addr) (netip.Addr, error) {\n\tif n, ok := s.syntheticIPs[addr]; ok {\n\t\tips, err := net.LookupIP(n)\n\t\tif err != nil {\n\t\t\treturn addr, err\n\t\t}\n\t\tif len(ips) == 0 {\n\t\t\treturn addr, fmt.Errorf(\"unable to resolve %s\", n)\n\t\t}\n\t\taddr, _ = netip.AddrFromSlice(ips[0])\n\t}\n\treturn addr, nil\n}\n\nfunc (s *session) ResolveName(addr netip.Addr) string {\n\treturn s.syntheticIPs[addr]\n}\n\nfunc (s *session) newCreateInterceptRequest(spec *manager.InterceptSpec) *manager.CreateInterceptRequest {\n\treturn &manager.CreateInterceptRequest{\n\t\tSession:       s.SessionInfo(),\n\t\tInterceptSpec: spec,\n\t}\n}\n\n// AddIntercept adds one intercept.\nfunc (s *session) AddIntercept(ctx context.Context, ir *rpc.CreateInterceptRequest) (*manager.InterceptInfo, error) {\n\tiInfo, err := s.CanIntercept(ctx, ir)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tspec := ir.Spec\n\tspec.Client = s.clientID\n\tif spec.Mechanism == \"\" {\n\t\tspec.Mechanism = \"tcp\"\n\t}\n\n\tmgrClient := s.ManagerClient()\n\n\t// iInfo.preparedIntercept == nil means that we're using an older traffic-manager, incapable\n\t// of using PrepareIntercept.\n\tpi := iInfo.PreparedIntercept()\n\n\tif spec.PortIdentifier == \"all\" {\n\t\tspec.PortIdentifier = \"\"\n\t} else if pi.ServicePort > 0 || pi.ServicePortName != \"\" {\n\t\t// Make spec port identifier unambiguous.\n\t\tspec.ServicePortName = pi.ServicePortName\n\t\tspec.ServicePort = pi.ServicePort\n\t\tpti, err := iInfo.PortIdentifier()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tspec.PortIdentifier = pti.String()\n\t}\n\tclog.Debugf(s, \"pi.Protocol = %s\", pi.Protocol)\n\tspec.Protocol = pi.Protocol\n\tspec.ContainerPort = pi.ContainerPort\n\tspec.ContainerName = pi.ContainerName\n\tif spec.NoDefaultPort {\n\t\tspec.Name = spec.Agent + \"/\" + pi.ContainerName\n\t}\n\tspec.PodPorts = pi.PodPorts\n\tif spec.TargetPort == 0 {\n\t\tspec.TargetPort = pi.ContainerPort\n\t}\n\n\tspec.ServiceUid = pi.ServiceUid\n\tspec.WorkloadKind = pi.WorkloadKind\n\n\tclog.Debugf(s, \"creating intercept %s\", spec.Name)\n\ttos := client.GetConfig(ctx).Timeouts()\n\tspec.RoundtripLatency = int64(tos.Get(client.TimeoutRoundtripLatency)) * 2 // Account for extra hop\n\tspec.DialTimeout = int64(tos.Get(client.TimeoutEndpointDial))\n\tc, cancel := tos.TimeoutContext(ctx, client.TimeoutIntercept)\n\tdefer cancel()\n\n\t// The agent is in place and the traffic-manager has acknowledged the creation of the intercept. It\n\t// should become active within a few seconds.\n\twaitCh := make(chan interceptResult, 2) // Need a buffer because reply can come before we're reading the channel,\n\ts.currentInterceptsLock.Lock()\n\ts.interceptWaiters[spec.Name] = &awaitIntercept{\n\t\tmountPoint: ir.MountPoint,\n\t\tmountPort:  ir.LocalMountPort,\n\t\treadOnly:   ir.MountReadOnly,\n\t\twaitCh:     waitCh,\n\t}\n\ts.currentInterceptsLock.Unlock()\n\tdefer func() {\n\t\ts.currentInterceptsLock.Lock()\n\t\tif _, ok := s.interceptWaiters[spec.Name]; ok {\n\t\t\tdelete(s.interceptWaiters, spec.Name)\n\t\t\tclose(waitCh)\n\t\t}\n\t\ts.currentInterceptsLock.Unlock()\n\t}()\n\n\tii, err := mgrClient.CreateIntercept(c, s.newCreateInterceptRequest(spec))\n\tif err != nil {\n\t\tclog.Debugf(c, \"manager responded to CreateIntercept with error %v\", err)\n\t\treturn nil, err\n\t}\n\n\tclog.Debugf(c, \"created intercept %s\", ii.Spec.Name)\n\n\tsuccess := false\n\tdefer func() {\n\t\tif !success {\n\t\t\tclog.Debugf(c, \"intercept %s failed to create, will remove...\", ii.Spec.Name)\n\t\t\tif removeErr := s.RemoveIntercept(ii.Spec.Name); removeErr != nil {\n\t\t\t\tclog.Warnf(c, \"failed to remove failed intercept %s: %v\", ii.Spec.Name, removeErr)\n\t\t\t}\n\t\t}\n\t}()\n\n\t// Wait for the intercept to transition from WAITING or NO_AGENT to ACTIVE. This\n\t// might result in more than one event.\n\tfor {\n\t\tselect {\n\t\tcase <-c.Done():\n\t\t\treturn nil, fmt.Errorf(\"failed to establish intercept: %w\", client.CheckTimeout(c, c.Err()))\n\t\tcase wr := <-waitCh:\n\t\t\tif wr.err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to establish intercept: %w\", wr.err)\n\t\t\t}\n\t\t\tic := wr.intercept\n\t\t\tii = ic.InterceptInfo\n\t\t\tif ii.Disposition != manager.InterceptDispositionType_ACTIVE {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-c.Done():\n\t\t\t\treturn nil, fmt.Errorf(\"failed to establish intercept: %w\", client.CheckTimeout(c, c.Err()))\n\t\t\tcase <-wr.mountsDone:\n\t\t\t}\n\t\t\terr := s.WithRootClient(ctx, func(ctx context.Context, rd daemon.DaemonClient) (err error) {\n\t\t\t\tenv, err := s.rootDaemon.TranslateEnvIPs(c, &daemon.Environment{Env: ii.Environment})\n\t\t\t\tif err == nil {\n\t\t\t\t\tii.Environment = env.Env\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn nil, client.CheckTimeout(c, err)\n\t\t\t}\n\t\t\tsuccess = true // Prevent removal in deferred function\n\t\t\treturn ii, nil\n\t\t}\n\t}\n}\n\n// RemoveIntercept removes one intercept by name.\nfunc (s *session) RemoveIntercept(name string) error {\n\tclog.Debugf(s, \"Removing intercept %s\", name)\n\n\t// Make an attempt to remove the created intercept using a time limited Context. Our\n\t// context is already done.\n\tctx, cancel := context.WithTimeout(context.WithoutCancel(s), 5*time.Second)\n\tdefer cancel()\n\tii := s.getInterceptByName(name)\n\tif ii == nil {\n\t\tclog.Debugf(ctx, \"Intercept %s was already removed\", name)\n\t\treturn nil\n\t}\n\treturn s.removeIntercept(ii)\n}\n\nfunc (s *session) removeIntercept(ic *intercept) error {\n\tname := ic.Spec.Name\n\ts.stopHandler(name, ic.handlerContainer, ic.pid)\n\n\t// Unmount filesystems before telling the manager to remove the intercept\n\tic.cancel()\n\tic.wg.Wait()\n\n\tc := s.Context\n\tclog.Debugf(c, \"telling manager to remove intercept %s\", name)\n\ttos := client.GetConfig(c).Timeouts()\n\tcc, cancel := tos.TimeoutContext(c, client.TimeoutTrafficManagerAPI)\n\tdefer cancel()\n\t_, err := s.ManagerClient().RemoveIntercept(cc, &manager.RemoveInterceptRequest2{\n\t\tSession: s.SessionInfo(),\n\t\tName:    name,\n\t})\n\tif err == nil {\n\t\tselect {\n\t\tcase <-c.Done():\n\t\tcase <-ic.finalRemovalDone:\n\n\t\t// Just in case the traffic-manager dies before it sends a new snapshot to our intercept watcher.\n\t\tcase <-time.After(tos.Get(client.TimeoutTrafficManagerAPI)):\n\t\t}\n\t}\n\treturn err\n}\n\nfunc (s *session) stopHandler(name, handlerContainer string, pid int) {\n\t// No use trying to kill processes when using a container-based daemon, unless\n\t// that daemon runs as a normal user daemon with a separate root daemon.\n\t// Some users run a standard telepresence client together with ingests/intercepts\n\t// in one single container.\n\tc := s.Context\n\tif !(proc.RunningInContainer() && s.GetService().RootSessionInProcess()) {\n\t\tif handlerContainer != \"\" {\n\t\t\tif err := docker.StopContainer(c, handlerContainer); err != nil {\n\t\t\t\t// It's possible that the container is stopped externally before we get here. If so,\n\t\t\t\t// then that's not an error.\n\t\t\t\tif !strings.Contains(err.Error(), \"No such container\") {\n\t\t\t\t\tclog.Error(c, err)\n\t\t\t\t}\n\t\t\t}\n\t\t} else if pid != 0 {\n\t\t\tp, err := os.FindProcess(pid)\n\t\t\tif err != nil {\n\t\t\t\tclog.Errorf(c, \"unable to find handler for ingest/intercept %s with pid %d\", name, pid)\n\t\t\t} else {\n\t\t\t\tclog.Debugf(c, \"terminating interceptor for ingest/intercept %s with pid %d\", name, pid)\n\t\t\t\t_ = proc.Terminate(p)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// AddInterceptor associates the given intercept with a running process. This ensures that\n// the running process will be signalled when the intercept is removed.\nfunc (s *session) AddInterceptor(id string, ih *rpc.Interceptor) error {\n\tadded := false\n\ts.currentInterceptsLock.Lock()\n\tif ci, ok := s.currentIntercepts[id]; ok {\n\t\tclog.Debugf(s, \"Adding intercept handler for id %s, %v\", id, ih)\n\t\tci.pid = int(ih.Pid)\n\t\tci.handlerContainer = ih.ContainerName\n\t\tadded = true\n\t} else {\n\t\tif parts := strings.Split(id, \"/\"); len(parts) == 2 {\n\t\t\tif cg, err := s.findIngest(parts[0], parts[1]); err == nil {\n\t\t\t\tclog.Debugf(s, \"Adding ingest handler for id %s, %v\", id, ih)\n\t\t\t\tcg.pid = int(ih.Pid)\n\t\t\t\tcg.handlerContainer = ih.ContainerName\n\t\t\t\tadded = true\n\t\t\t}\n\t\t}\n\t}\n\ts.currentInterceptsLock.Unlock()\n\tif !added {\n\t\treturn status.Error(codes.NotFound, fmt.Sprintf(\"no intercept or ingest with id %s\", id))\n\t}\n\treturn nil\n}\n\nfunc (s *session) RemoveInterceptor(id string) error {\n\ts.currentInterceptsLock.Lock()\n\tif ci, ok := s.currentIntercepts[id]; ok {\n\t\tci.pid = 0\n\t\tci.handlerContainer = \"\"\n\t} else {\n\t\tif parts := strings.Split(id, \"/\"); len(parts) == 2 {\n\t\t\tif cg, err := s.findIngest(parts[0], parts[1]); err == nil {\n\t\t\t\tcg.pid = 0\n\t\t\t\tcg.handlerContainer = \"\"\n\t\t\t}\n\t\t}\n\t}\n\ts.currentInterceptsLock.Unlock()\n\treturn nil\n}\n\n// GetInterceptSpec returns the InterceptSpec for the given name, or nil if no such spec exists.\nfunc (s *session) GetInterceptSpec(name string) *manager.InterceptSpec {\n\tif ic := s.getInterceptByName(name); ic != nil {\n\t\treturn ic.Spec\n\t}\n\treturn nil\n}\n\n// GetInterceptInfo returns the InterceptInfo for the given name, or nil if no such info exists.\nfunc (s *session) GetInterceptInfo(name string) *manager.InterceptInfo {\n\tif ic := s.getInterceptByName(name); ic != nil {\n\t\tii := ic.InterceptInfo\n\t\tif ic.handlerContainer != \"\" {\n\t\t\tif ii.Environment == nil {\n\t\t\t\tii.Environment = make(map[string]string, 1)\n\t\t\t}\n\t\t\tii.Environment[\"TELEPRESENCE_HANDLER_CONTAINER_NAME\"] = ic.handlerContainer\n\t\t}\n\t\treturn ii\n\t}\n\treturn nil\n}\n\n// GetInterceptSpec returns the InterceptSpec for the given name, or nil if no such spec exists.\nfunc (s *session) getInterceptByName(name string) *intercept {\n\ts.currentInterceptsLock.Lock()\n\tdefer s.currentInterceptsLock.Unlock()\n\tfor _, ic := range s.currentIntercepts {\n\t\tif ic.Spec.Name == name {\n\t\t\treturn ic\n\t\t}\n\t}\n\n\tif slashIx := strings.IndexByte(name, '/'); slashIx > 0 {\n\t\tcontainer := name[slashIx+1:]\n\t\tname = name[:slashIx]\n\t\tfor _, ic := range s.currentIntercepts {\n\t\t\tif ic.Spec.Name == name && container == ic.Spec.ContainerName {\n\t\t\t\treturn ic\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// Check if the name uniquely identifies a `replace` by its workload (always uses <workload>/<container>)\n\tnamePfx := name + \"/\"\n\tvar found *intercept\n\tfor _, ic := range s.currentIntercepts {\n\t\tif strings.HasPrefix(ic.Spec.Name, namePfx) {\n\t\t\tif found != nil {\n\t\t\t\t// Found a second time using prefix, so the prefix isn't unique and hence not valid.\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tfound = ic\n\t\t}\n\t}\n\tif found != nil {\n\t\t// Name is not unique if it also identifies an ingest with the same workload.\n\t\ts.currentIngests.Range(func(key ingestKey, ig *ingest) bool {\n\t\t\tif key.workload == name {\n\t\t\t\tfound = nil\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n\treturn found\n}\n\n// InterceptsForWorkload returns the client's current intercepts on the given namespace and workload combination.\nfunc (s *session) InterceptsForWorkload(workloadName, namespace string) []*manager.InterceptSpec {\n\twlis := make([]*manager.InterceptSpec, 0)\n\tfor _, ic := range s.getCurrentIntercepts() {\n\t\tif ic.Spec.Agent == workloadName && ic.Spec.Namespace == namespace {\n\t\t\twlis = append(wlis, ic.Spec)\n\t\t}\n\t}\n\treturn wlis\n}\n\n// ClearIngestsAndIntercepts removes all intercepts.\nfunc (s *session) ClearIngestsAndIntercepts() error {\n\tfor _, ic := range s.getCurrentIntercepts() {\n\t\tclog.Debugf(s, \"Clearing intercept %s\", ic.Spec.Name)\n\t\terr := s.removeIntercept(ic)\n\t\tif err != nil && status.Code(err) != codes.NotFound {\n\t\t\treturn err\n\t\t}\n\t}\n\ts.currentIngests.Range(func(key ingestKey, ig *ingest) bool {\n\t\tclog.Debugf(s, \"Clearing ingest %s\", key)\n\t\ts.stopHandler(key.workload+\"/\"+key.container, ig.handlerContainer, ig.pid)\n\t\treturn true\n\t})\n\treturn nil\n}\n\n// reconcileAPIServers start/stop API servers as needed based on the TELEPRESENCE_API_PORT environment variable\n// of the currently intercepted agent's env.\nfunc (s *session) reconcileAPIServers() {\n\twantedPorts := make(map[int]struct{})\n\twantedMatchers := make(map[string]*manager.InterceptInfo)\n\n\tagentAPIPort := func(ii *manager.InterceptInfo) int {\n\t\tis := ii.Spec\n\t\tif ps, ok := ii.Environment[agentconfig.EnvAPIPort]; ok {\n\t\t\tport, err := strconv.ParseUint(ps, 10, 16)\n\t\t\tif err == nil {\n\t\t\t\treturn int(port)\n\t\t\t}\n\t\t\tclog.Errorf(s, \"unable to parse TELEPRESENCE_API_PORT(%q) to a port number in agent %s.%s: %v\", ps, is.Agent, is.Namespace, err)\n\t\t}\n\t\treturn 0\n\t}\n\n\tfor _, ic := range s.currentIntercepts {\n\t\tii := ic.InterceptInfo\n\t\tif ic.Disposition == manager.InterceptDispositionType_ACTIVE {\n\t\t\tif port := agentAPIPort(ii); port > 0 {\n\t\t\t\twantedPorts[port] = struct{}{}\n\t\t\t\twantedMatchers[ic.Id] = ii\n\t\t\t}\n\t\t}\n\t}\n\tfor p, as := range s.currentAPIServers {\n\t\tif _, ok := wantedPorts[p]; !ok {\n\t\t\tas.cancel()\n\t\t\tdelete(s.currentAPIServers, p)\n\t\t}\n\t}\n\tfor p := range wantedPorts {\n\t\tif _, ok := s.currentAPIServers[p]; !ok {\n\t\t\ts.newAPIServerForPort(p)\n\t\t}\n\t}\n\tfor id := range s.currentMatchers {\n\t\tif _, ok := wantedMatchers[id]; !ok {\n\t\t\tdelete(s.currentMatchers, id)\n\t\t}\n\t}\n\tfor id, ic := range wantedMatchers {\n\t\tif _, ok := s.currentMatchers[id]; !ok {\n\t\t\ts.newMatcher(ic)\n\t\t}\n\t}\n}\n\nfunc (s *session) newAPIServerForPort(port int) {\n\tsvr := restapi.NewServer(s)\n\tctx, cancel := context.WithCancel(s)\n\tas := apiServer{Server: svr, cancel: cancel}\n\tif s.currentAPIServers == nil {\n\t\ts.currentAPIServers = map[int]*apiServer{port: &as}\n\t} else {\n\t\ts.currentAPIServers[port] = &as\n\t}\n\tgo func() {\n\t\tif err := svr.ListenAndServe(ctx, port); err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t}\n\t}()\n}\n\nfunc (s *session) newMatcher(ic *manager.InterceptInfo) {\n\tm := matcher.NewRequest(ic.Spec.PathFilters, ic.Spec.HeaderFilters)\n\tif s.currentMatchers == nil {\n\t\ts.currentMatchers = make(map[string]*apiMatcher)\n\t}\n\ts.currentMatchers[ic.Id] = &apiMatcher{\n\t\trequestMatcher: m,\n\t\tmetadata:       ic.Spec.Metadata,\n\t}\n}\n\nfunc (s *session) InterceptInfo(_ context.Context, callerID, path string, _ uint16, headers http.Header) (*restapi.InterceptInfo, error) {\n\ts.currentInterceptsLock.Lock()\n\tdefer s.currentInterceptsLock.Unlock()\n\n\tr := &restapi.InterceptInfo{ClientSide: true}\n\tam := s.currentMatchers[callerID]\n\tswitch {\n\tcase am == nil:\n\t\tclog.Debugf(s, \"no matcher found for callerID %s\", callerID)\n\tcase am.requestMatcher.MatchesPathAndHeader(path, headers):\n\t\tclog.Debugf(s, \"%s: matcher %s matches path %q and headers %s\", callerID, am.requestMatcher, path, matcher.HeaderStringer(headers))\n\t\tr.Intercepted = true\n\t\tr.Metadata = am.metadata\n\tdefault:\n\t\tclog.Debugf(s, \"%s: matcher %s does not matches path %q and headers %s\", callerID, am.requestMatcher, path, matcher.HeaderStringer(headers))\n\t}\n\treturn r, nil\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/mount.go",
    "content": "package trafficmgr\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"sync\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/go-fuseftp/rpc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/remotefs\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n)\n\nfunc (pa *podAccess) shouldMount() bool {\n\treturn (pa.ftpPort > 0 || pa.sftpPort > 0) && (pa.localMountPort > 0 || pa.clientMountPoint != \"\")\n}\n\n// startMount starts the mount for the given podAccessKey.\n// It assumes that the user has called shouldMount and is sure that something will be started.\nfunc (pa *podAccess) startMount(ctx context.Context, iceptWG, podWG *sync.WaitGroup) {\n\tvar fuseftp rpc.FuseFTPClient\n\tuseFtp := client.GetConfig(ctx).Intercept().UseFtp\n\taddr, err := netip.ParseAddr(pa.podIP)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"error parsing pod IP address %q: %v\", pa.podIP, err)\n\t\treturn\n\t}\n\tvar port uint16\n\tmountCtx := ctx\n\tif useFtp {\n\t\tif pa.ftpPort == 0 {\n\t\t\tclog.Error(ctx, \"Client is configured to perform remote mounts using FTP, but only SFTP is provided by the traffic-agent\")\n\t\t\treturn\n\t\t}\n\t\tif pa.localMountPort > 0 {\n\t\t\tclog.Error(ctx, \"Client is configured to perform remote mounts using FTP, but only SFTP can be used with --local-mount-port\")\n\t\t\treturn\n\t\t}\n\t\t// The FTP mounter survives multiple starts for the same intercept. It just resets the address\n\t\tmountCtx = pa.ctx\n\t\tfuseftp = getSession(ctx).GetService().FuseFTPMgr().GetFuseFTPClient(ctx)\n\t\tif fuseftp == nil {\n\t\t\tclog.Error(ctx, \"Client is configured to perform remote mounts using FTP, but the fuseftp server was unable to start\")\n\t\t\treturn\n\t\t}\n\t\tport = uint16(pa.ftpPort)\n\t} else {\n\t\tif pa.sftpPort == 0 {\n\t\t\tclog.Error(ctx, \"Client is configured to perform remote mounts using SFTP, but only FTP is provided by the traffic-agent\")\n\t\t\treturn\n\t\t}\n\t\tport = uint16(pa.sftpPort)\n\t}\n\n\tm := *pa.mounter\n\tif m == nil {\n\t\tswitch {\n\t\tcase pa.localMountPort != 0:\n\t\t\tsession := getSession(ctx)\n\t\t\tm = remotefs.NewBridgeMounter(tunnel.SessionID(session.SessionInfo().SessionId), session.ManagerClient(), uint16(pa.localMountPort))\n\t\tcase useFtp:\n\t\t\tm = remotefs.NewFTPMounter(fuseftp, iceptWG)\n\t\tdefault:\n\t\t\tm = remotefs.NewSFTPMounter(iceptWG, podWG)\n\t\t}\n\t\t*pa.mounter = m\n\t}\n\terr = m.Start(mountCtx, pa.workload, pa.container, pa.clientMountPoint, pa.mountPoint, netip.AddrPortFrom(addr, port), pa.readOnly)\n\tif err != nil && ctx.Err() == nil {\n\t\tclog.Error(ctx, err)\n\t}\n}\n\nfunc (s *session) ensureNoMountConflict(localMountPoint string, localMountPort int32) (err error) {\n\tif localMountPoint == \"\" && localMountPort == 0 {\n\t\treturn nil\n\t}\n\ts.currentInterceptsLock.Lock()\n\tfor _, ic := range s.currentIntercepts {\n\t\tif localMountPoint != \"\" && ic.ClientMountPoint == localMountPoint {\n\t\t\terr = status.Errorf(codes.AlreadyExists, \"mount point %s already in use by intercept %s\", localMountPoint, ic.Spec.Name)\n\t\t\tbreak\n\t\t}\n\t\tif localMountPort != 0 && ic.localMountPort == localMountPort {\n\t\t\terr = status.Errorf(codes.AlreadyExists, \"mount port %d already in use by intercept %s\", localMountPort, ic.Spec.Name)\n\t\t\tbreak\n\t\t}\n\t}\n\ts.currentInterceptsLock.Unlock()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.currentIngests.Range(func(key ingestKey, ig *ingest) bool {\n\t\tif localMountPoint != \"\" && ig.localMountPoint == localMountPoint {\n\t\t\terr = status.Errorf(codes.AlreadyExists, \"mount point %s already in use by ingest %s\", localMountPoint, key)\n\t\t\treturn false\n\t\t}\n\t\tif localMountPort != 0 && ig.localMountPort == localMountPort {\n\t\t\terr = status.Errorf(codes.AlreadyExists, \"mount port %d already in use by ingest %s\", localMountPort, key)\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn err\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/podaccess.go",
    "content": "package trafficmgr\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"sync\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/types/known/durationpb\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/remotefs\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/forwarder\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype podAccess struct {\n\t// ctx is a context canceled by the cancel attribute. It must be used by\n\t// services that should be canceled when the ingest or intercept ends\n\tctx context.Context\n\n\t// wg is the group to wait for after a call to cancel\n\twg *sync.WaitGroup\n\n\tlocalPorts       []string\n\tworkload         string\n\tcontainer        string\n\tpodIP            string\n\tsftpPort         int32\n\tftpPort          int32\n\tmountPoint       string\n\tclientMountPoint string\n\n\t// Pointer to the mounter of the remote file system. The mounter\n\t// is maintained in the ingest or intercept structure\n\tmounter *remotefs.Mounter\n\n\t// Use bridged ftp/sftp mount through this local port\n\tlocalMountPort int32\n\n\t// Mount read-only\n\treadOnly bool\n}\n\n// podAccessKey identifies an intercepted pod. Although an ingest or intercept may span multiple\n// pods, the user daemon will always choose exactly one pod with an active ingest or intercept to\n// do port forwards and remote mounts.\ntype podAccessKey struct {\n\tcontainer string\n\tpodIP     string\n}\n\n// The podAccessSync provides pod-specific synchronization for cancellation of port forwards\n// and mounts. Cancellation here does not mean that the ingest or intercept is canceled. It just\n// means that the given pod is no longer the chosen one. This typically happens when pods\n// are scaled down and then up again.\ntype podAccessSync struct {\n\tworkload  string\n\twg        sync.WaitGroup\n\tcancelPod context.CancelFunc\n}\n\n// podAccessTracker is what the traffic-manager is using to keep track of the chosen pods for\n// the currently active intercepts.\ntype podAccessTracker struct {\n\tsync.Mutex\n\n\t// alive contains a map of the currently tracked podAccessSync\n\talivePods map[podAccessKey]*podAccessSync\n\n\t// A snapshot is recreated for each new ingest or intercept snapshot read from the manager.\n\t// The set controls which podAccessTracker that are considered alive when cancelUnwanted\n\t// is called\n\tsnapshot map[podAccessKey]struct{}\n\n\t// mountsReady contains channels that are closed when the mounts are prepared\n\tmountsReady map[podAccessKey]chan struct{}\n}\n\nfunc (pa *podAccess) shouldForward() bool {\n\treturn len(pa.localPorts) > 0\n}\n\n// startForwards starts port forwards and mounts for the given podAccessKey.\n// It assumes that the user has called shouldForward and is sure that something will be started.\nfunc (pa *podAccess) startForwards(ctx context.Context, wg *sync.WaitGroup) {\n\tfor _, port := range pa.localPorts {\n\t\tvar pfCtx context.Context\n\t\tif iputil.IsIpV6Addr(pa.podIP) {\n\t\t\tpfCtx = clog.WithGroup(ctx, fmt.Sprintf(\"[%s]:%s\", pa.podIP, port))\n\t\t} else {\n\t\t\tpfCtx = clog.WithGroup(ctx, fmt.Sprintf(\"%s:%s\", pa.podIP, port))\n\t\t}\n\t\twg.Add(1)\n\t\tgo pa.workerPortForward(pfCtx, port, wg)\n\t}\n}\n\nfunc (pa *podAccess) ensureAccess(ctx context.Context, rd daemon.DaemonClient) error {\n\tcc := client.GetConfig(ctx)\n\tif cc.Cluster().AgentPortForward {\n\t\t// An agent port-forward to the pod with a designated to the podIP is necessary to\n\t\t// mount or port-forward to localhost.\n\t\tclog.Debugf(ctx, \"Waiting for root-daemon to receive agent IP %s\", pa.podIP)\n\t\tip, err := netip.ParseAddr(pa.podIP)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trsp, err := rd.WaitForAgentIP(ctx, &daemon.WaitForAgentIPRequest{\n\t\t\tIp:      ip.AsSlice(),\n\t\t\tTimeout: durationpb.New(cc.Timeouts().Get(client.TimeoutIntercept)),\n\t\t})\n\t\tswitch status.Code(err) {\n\t\tcase codes.Unavailable: // Unavailable means that the feature disabled. This is OK, the traffic-manager will do the forwarding\n\t\tcase codes.OK:\n\t\t\tif lip, ok := netip.AddrFromSlice(rsp.LocalIp); ok {\n\t\t\t\tpa.podIP = lip.String()\n\t\t\t}\n\t\tcase codes.DeadlineExceeded:\n\t\t\treturn fmt.Errorf(\"timeout waiting for port-forward to traffic-agent with pod-ip %s\", pa.podIP)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"unexpected error for port-forward to traffic-agent with pod-ip %s: %v\", pa.podIP, err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (pa *podAccess) workerPortForward(ctx context.Context, port string, wg *sync.WaitGroup) {\n\tdefer wg.Done()\n\tpp, err := types.ParsePortAndProto(port)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"malformed extra port %q: %v\", port, err)\n\t\treturn\n\t}\n\taddr, err := netip.ParseAddr(pa.podIP)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"error parsing pod IP address %q: %v\", pa.podIP, err)\n\t\treturn\n\t}\n\tf := forwarder.New(pp, tunnel.ClientToAgent, netip.AddrPortFrom(addr, pp.Port))\n\terr = f.Serve(ctx, nil)\n\tif err != nil && ctx.Err() == nil {\n\t\tclog.Errorf(ctx, \"port-forwarder failed with %v\", err)\n\t}\n}\n\nfunc newPodAccessTracker() *podAccessTracker {\n\treturn &podAccessTracker{alivePods: make(map[podAccessKey]*podAccessSync)}\n}\n\n// start a port forward for the given ingest or intercept and remembers that it's alive.\nfunc (lpf *podAccessTracker) start(pa *podAccess) {\n\t// The mounts performed here are synced on by podIP + port to keep track of active\n\t// mounts. This is not enough in situations when a pod is deleted and another pod\n\t// takes over. That is two different IPs so an additional synchronization on the actual\n\t// mount point is necessary to prevent that it is established and deleted at the same\n\t// time.\n\tlpf.Lock()\n\tfk := podAccessKey{\n\t\tcontainer: pa.container,\n\t\tpodIP:     pa.podIP,\n\t}\n\n\tdefer func() {\n\t\tif md, ok := lpf.mountsReady[fk]; ok {\n\t\t\tdelete(lpf.mountsReady, fk)\n\t\t\tclose(md)\n\t\t}\n\t}()\n\n\t// Make part of current snapshot tracking so that it isn't removed once the\n\t// snapshot has been completely handled\n\tlpf.snapshot[fk] = struct{}{}\n\tlpf.privateStart(pa)\n\tlpf.Unlock()\n}\n\nfunc (lpf *podAccessTracker) initialStart(ic *podAccess) {\n\tlpf.Lock()\n\tlpf.privateStart(ic)\n\tlpf.Unlock()\n}\n\nfunc (lpf *podAccessTracker) privateStart(pa *podAccess) {\n\tctx := pa.ctx\n\tif !pa.shouldForward() && !pa.shouldMount() {\n\t\tclog.Debugf(ctx, \"No mounts or port-forwards needed for pod-ip %s, container %s\", pa.podIP, pa.container)\n\t\treturn\n\t}\n\n\t// Already started?\n\tfk := podAccessKey{\n\t\tcontainer: pa.container,\n\t\tpodIP:     pa.podIP,\n\t}\n\tif _, isLive := lpf.alivePods[fk]; isLive {\n\t\tclog.Debugf(ctx, \"Mounts and port-forwards already active for %+v\", fk)\n\t\treturn\n\t}\n\n\tctx, cancel := context.WithCancel(pa.ctx)\n\tlp := &podAccessSync{workload: pa.workload, cancelPod: cancel}\n\tif pa.shouldMount() {\n\t\tpa.startMount(ctx, pa.wg, &lp.wg)\n\t}\n\tif pa.shouldForward() {\n\t\tpa.startForwards(ctx, &lp.wg)\n\t}\n\tlpf.alivePods[fk] = lp\n\tclog.Debugf(ctx, \"Started mounts and port-forwards for pod-ip %s, container %s\", pa.podIP, pa.container)\n}\n\n// initSnapshot prepares this instance for a new round of start calls followed by a cancelUnwanted.\nfunc (lpf *podAccessTracker) initSnapshot() {\n\tlpf.Lock()\n\tlpf.snapshot = make(map[podAccessKey]struct{})\n\tlpf.mountsReady = make(map[podAccessKey]chan struct{})\n\tlpf.Unlock()\n}\n\nfunc (lpf *podAccessTracker) getOrCreateMountsDone(pa *podAccess) <-chan struct{} {\n\tfk := podAccessKey{\n\t\tcontainer: pa.container,\n\t\tpodIP:     pa.podIP,\n\t}\n\tlpf.Lock()\n\tmd, ok := lpf.mountsReady[fk]\n\tif !ok {\n\t\tmd = make(chan struct{})\n\t\tlpf.mountsReady[fk] = md\n\t}\n\tlpf.Unlock()\n\treturn md\n}\n\nfunc (lpf *podAccessTracker) privateDelete(fk podAccessKey, lp *podAccessSync) {\n\tdelete(lpf.alivePods, fk)\n\tmd, ok := lpf.mountsReady[fk]\n\tif ok {\n\t\tdelete(lpf.mountsReady, fk)\n\t\tclose(md)\n\t}\n\tlpf.Unlock()\n\tlp.cancelPod()\n\tlp.wg.Wait()\n\tlpf.Lock()\n}\n\n// cancelContainer cancels mounts and port forwards for the given container.\nfunc (lpf *podAccessTracker) cancelContainer(workload, container string) {\n\tlpf.Lock()\n\tfor fk, lp := range lpf.alivePods {\n\t\tif fk.container == container && lp.workload == workload {\n\t\t\tlpf.privateDelete(fk, lp)\n\t\t}\n\t}\n\tlpf.Unlock()\n}\n\n// cancelUnwanted cancels all mounts and port forwards that haven't been started since initSnapshot.\nfunc (lpf *podAccessTracker) cancelUnwanted(ctx context.Context) {\n\tlpf.Lock()\n\tfor fk, lp := range lpf.alivePods {\n\t\tif _, isWanted := lpf.snapshot[fk]; !isWanted {\n\t\t\tclog.Infof(ctx, \"Terminating mounts and port-forwards for %+v\", fk)\n\t\t\tlpf.privateDelete(fk, lp)\n\t\t}\n\t}\n\tlpf.Unlock()\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/session.go",
    "content": "package trafficmgr\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/user\"\n\t\"slices\"\n\t\"sort\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/blang/semver/v4\"\n\t\"github.com/google/uuid\"\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"google.golang.org/protobuf/proto\"\n\tempty \"google.golang.org/protobuf/types/known/emptypb\"\n\t\"google.golang.org/protobuf/types/known/timestamppb\"\n\tk8sTypes \"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/util/homedir\"\n\t\"sigs.k8s.io/yaml\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/connector\"\n\trootdRpc \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/authenticator/patcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/k8s\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/rootd\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/userd\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/forwarder\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/grpc/watcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/json\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/matcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/restapi\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/subnet\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tmconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/workload\"\n)\n\ntype apiServer struct {\n\trestapi.Server\n\tcancel context.CancelFunc\n}\n\ntype apiMatcher struct {\n\trequestMatcher matcher.Request\n\tmetadata       map[string]string\n}\n\ntype workloadInfoKey struct {\n\tkind manager.WorkloadInfo_Kind\n\tname string\n}\n\ntype workloadInfo struct {\n\tuid              k8sTypes.UID\n\tstate            workload.State\n\tagentState       manager.WorkloadInfo_AgentState\n\tinterceptClients []string\n}\n\ntype session struct {\n\t*k8s.Cluster\n\tservice            userd.Service\n\trootDaemon         rootdRpc.DaemonClient\n\tsubnetViaWorkloads []*rootdRpc.SubnetViaWorkload\n\n\t// local information\n\tinstallID string // telepresence's install ID\n\tclientID  string // \"laptop-username@laptop-hostname\"\n\n\t// manager client connection\n\tmanagerConn *grpc.ClientConn\n\n\t// name reported by the manager\n\tmanagerName string\n\n\t// version reported by the manager\n\tmanagerVersion semver.Version\n\n\t// The identifier for this daemon\n\tdaemonID *daemon.Identifier\n\n\tsessionInfo *manager.SessionInfo // sessionInfo returned by the traffic-manager\n\n\tworkloadsLock sync.Mutex\n\n\t// Map of manager.WorkloadInfo split into namespace, key of kind and name, and workloadInfo\n\tworkloads map[string]map[workloadInfoKey]workloadInfo\n\n\tworkloadSubscribers map[uuid.UUID]chan struct{}\n\n\t// currentIngests is tracks the ingests that are active in this session.\n\tcurrentIngests *xsync.Map[ingestKey, *ingest]\n\n\tingestTracker *podAccessTracker\n\n\t// currentInterceptsLock ensures that all accesses to currentAgents, currentIntercepts, currentMatchers,\n\t// currentAPIServers, interceptWaiters, and ingressInfo are synchronized\n\t//\n\tcurrentInterceptsLock sync.Mutex\n\n\t// currentAgents is the latest snapshot returned by the agents watcher.\n\tcurrentAgents []*manager.AgentInfo\n\n\t// currentIntercepts is the latest snapshot returned by the intercept watcher. It\n\t// is keyeed by the intercept ID\n\tcurrentIntercepts map[string]*intercept\n\n\t// currentMatches hold the matchers used when using the APIServer.\n\tcurrentMatchers map[string]*apiMatcher\n\n\t// currentAPIServers contains the APIServer in use. Typically zero or only one, but since the\n\t// port is determined by the intercept, there might theoretically be serveral.\n\tcurrentAPIServers map[int]*apiServer\n\n\t// Map of desired awaited intercepts. Keyed by intercept name, because it\n\t// is filled in prior to the intercept being created. Entries are short lived. They\n\t// are deleted as soon as the intercept arrives and gets stored in currentIntercepts\n\tinterceptWaiters map[string]*awaitIntercept\n\n\tisPodDaemon bool\n\n\t// Synthetic IPs are generated when the targetIP is a hostname, so that we can defer the\n\t// lookup of that host until the time when it is dialed.\n\tsyntheticIPs map[netip.Addr]string\n\n\t// lastActivity is set when something happens to the session that counts as proof that the\n\t// client is in use. All gRPC calls involving a session will update this timestamp, and\n\t// the root daemon will also update this timestamp when a new TCP tunnel is created to\n\t// a traffic-agent. The root daemon will not update this timestamp when resolving DNS\n\t// calls because they often arrive sporadically due to activity that isn't related to\n\t// Telepresence at all.\n\tlastActivity int64\n}\n\nfunc (s *session) RevokeIntercept(ctx context.Context, interceptID string) error {\n\treturn tmconfig.AddCommand(s, k8s.GetManagerNamespace(ctx), tmconfig.AdminCommand{\n\t\tName:      tmconfig.RemoveIntercept,\n\t\tArgs:      []string{interceptID},\n\t\tTimestamp: time.Now().UnixNano(),\n\t})\n}\n\nfunc NewSession(\n\tservice userd.Service,\n\tctx context.Context,\n\tcr *rpc.ConnectRequest,\n\tconfig *k8s.Kubeconfig,\n\twg *sync.WaitGroup,\n) (session userd.Session, info *rpc.ConnectInfo, err error) {\n\tclog.Info(config, \"-- Starting new session\")\n\n\tclog.Infof(config, \"Connecting to k8s context %s (%s) ...\", config.KubeContext, config.Server)\n\n\tcluster, err := k8s.ConnectCluster(cr, config)\n\tif err != nil {\n\t\tclog.Errorf(config, \"unable to track k8s cluster: %+v\", err)\n\t\treturn nil, nil, err\n\t}\n\tclog.Infof(cluster, \"Connected to context %s, namespace %s (%s)\", cluster.KubeContext, cluster.Namespace, cluster.Server)\n\n\tclog.Info(cluster, \"Connecting to traffic manager...\")\n\tinstallID, err := client.InstallID(cluster)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\tcfg := client.GetConfig(cluster)\n\ttos := cfg.Timeouts()\n\tctx, cancel := tos.TimeoutContext(ctx, client.TimeoutTrafficManagerConnect)\n\tdefer cancel()\n\n\ttmgr, err := connectMgr(ctx, service, cluster, installID, cr)\n\tif err != nil {\n\t\tclog.Errorf(config, \"Unable to connect to session: %s\", err)\n\t\treturn nil, nil, err\n\t}\n\tif tmgr.compareFinalizedManagerVersion(2, 21, 0) < 0 {\n\t\treturn nil, nil,\n\t\t\tfmt.Errorf(\"traffic manager version %s is too old. Minimum supported version is 2.21.0, please upgrade\", tmgr.managerVersion)\n\t}\n\ttmgr.updateClientConfig(ctx, cr.MappedNamespaces)\n\n\toi := tmgr.getNetworkInfo(cr)\n\tif !service.RootSessionInProcess() {\n\t\t// Root daemon needs this to authenticate with the cluster. Potential exec configurations in the kubeconfig\n\t\t// must be executed by the user, not by root.\n\t\toi.KubeconfigData, err = patcher.CreateExternalKubeConfig(tmgr.Context, config.ClientConfig, tmgr.KubeContext, func([]string) (string, string, string, error) {\n\t\t\treturn client.GetExe(tmgr), service.ListenerAddress().String(), client.GetConfigFile(tmgr), nil\n\t\t}, nil)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t}\n\n\ttmgr.Context = tunnel.WithSyntheticIPResolver(tmgr.Context, tmgr)\n\n\ttmgr.rootDaemon, err = tmgr.connectRootDaemon(ctx, oi, wg, cr.IsPodDaemon)\n\tif err != nil {\n\t\ttmgr.managerConn.Close()\n\t\treturn nil, nil, err\n\t}\n\n\t// Collect data on how long connection time took\n\tclog.Debug(tmgr, \"Finished connecting to traffic manager\")\n\n\ttmgr.AddNamespaceEventHandler(tmgr.updateDaemonNamespaces)\n\tci, err := tmgr.status(ctx, true)\n\treturn tmgr, ci, err\n}\n\nfunc (s *session) GetService() userd.Service {\n\treturn s.service\n}\n\n// Run (1) starts up with ensuring that the manager is installed and running,\n// but then for most of its life\n//   - (2) calls manager.ArriveAsClient and then periodically calls manager.Remain\n//   - run the intercepts (manager.WatchIntercepts) and then\n//   - (3) listen on the appropriate local ports and forward them to the intercepted\n//     Services, and\n//   - (4) mount the appropriate remote volumes.\nfunc (s *session) Run() {\n\tg := log.NewGroup(s)\n\tdefer func() {\n\t\t_ = s.WithRootClient(context.WithoutCancel(s), func(ctx context.Context, rd rootdRpc.DaemonClient) error {\n\t\t\t_, _ = rd.Disconnect(ctx, &empty.Empty{})\n\t\t\treturn nil\n\t\t})\n\t\tclog.Info(s, \"-- session ended\")\n\t}()\n\ts.startServices(g)\n\terr := g.Wait()\n\tif err != nil {\n\t\tclog.Errorf(s, \"session ended with error: %v\", err)\n\t}\n}\n\nfunc (s *session) WithRootClient(ctx context.Context, f func(context.Context, rootdRpc.DaemonClient) error) error {\n\treturn f(ctx, s.rootDaemon)\n}\n\nfunc (s *session) ManagerClient() manager.ManagerClient {\n\treturn manager.NewManagerClient(s.managerConn)\n}\n\nfunc (s *session) ManagerName() string {\n\treturn s.managerName\n}\n\nfunc (s *session) ManagerVersion() semver.Version {\n\treturn s.managerVersion\n}\n\nfunc (s *session) MarkActivity() {\n\tatomic.StoreInt64(&s.lastActivity, time.Now().UnixNano())\n}\n\n// connectMgr returns a session for the given cluster that is connected to the traffic-manager.\nfunc connectMgr(\n\ttimeoutCtx context.Context,\n\tservice userd.Service,\n\tcluster *k8s.Cluster,\n\tinstallID string,\n\tcr *rpc.ConnectRequest,\n) (*session, error) {\n\tcfg := client.GetConfig(cluster)\n\tmgrNs := k8s.GetManagerNamespace(cluster)\n\tconn, managerName, managerVersion, err := cluster.ConnectToManager(timeoutCtx, mgrNs)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif sdc := cfg.Grpc().SimulateDisconnect; sdc > 0 {\n\t\ttime.AfterFunc(sdc, func() {\n\t\t\tclog.Info(cluster, \"Simulated disconnect from manager\")\n\t\t\tconn.Close()\n\t\t})\n\t}\n\n\tclientID := cr.ClientId\n\tif clientID == \"\" {\n\t\tuserinfo, err := user.Current()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to obtain current user: %w\", err)\n\t\t}\n\t\thost, err := os.Hostname()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to obtain hostname: %w\", err)\n\t\t}\n\n\t\tclientID = fmt.Sprintf(\"%s@%s\", userinfo.Username, host)\n\t}\n\n\tdaemonID := daemon.NewIdentifier(cr.Name, cluster.KubeContext, cluster.Namespace, proc.RunningInContainer())\n\tsi, err := LoadSessionInfoFromUserCache(cluster, daemonID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tmClient := manager.NewManagerClient(conn)\n\tif si != nil {\n\t\t// Check if the session is still valid in the traffic-manager by calling Remain\n\t\t_, err = mClient.Remain(timeoutCtx, &manager.RemainRequest{Session: si})\n\t\tif err == nil {\n\t\t\tif timeoutCtx.Err() != nil {\n\t\t\t\t// Call timed out, so the traffic-manager isn't responding at all\n\t\t\t\treturn nil, timeoutCtx.Err()\n\t\t\t}\n\t\t\tclog.Debugf(cluster, \"traffic-manager port-forward established, client was already known to the traffic-manager as %q\", clientID)\n\t\t} else {\n\t\t\tsi = nil\n\t\t}\n\t}\n\n\tif si == nil {\n\t\tclog.Debugf(cluster, \"traffic-manager port-forward established, making client known to the traffic-manager as %q\", clientID)\n\t\tsi, err = mClient.ArriveAsClient(timeoutCtx, &manager.ClientInfo{\n\t\t\tName:      clientID,\n\t\t\tNamespace: cluster.Namespace,\n\t\t\tInstallId: installID,\n\t\t\tProduct:   \"telepresence\",\n\t\t\tVersion:   client.Version(),\n\t\t})\n\t\tif err != nil {\n\t\t\tif st, ok := status.FromError(err); ok && st.Code() == codes.FailedPrecondition {\n\t\t\t\treturn nil, errcat.User.New(st.Message())\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t\tif err = saveSessionInfoToUserCache(timeoutCtx, daemonID, si); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tsess := &session{\n\t\tCluster:            cluster,\n\t\tservice:            service,\n\t\tinstallID:          installID,\n\t\tdaemonID:           daemonID,\n\t\tclientID:           clientID,\n\t\tmanagerConn:        conn,\n\t\tmanagerName:        managerName,\n\t\tmanagerVersion:     managerVersion,\n\t\tsessionInfo:        si,\n\t\tcurrentIngests:     xsync.NewMap[ingestKey, *ingest](),\n\t\tingestTracker:      newPodAccessTracker(),\n\t\tworkloads:          make(map[string]map[workloadInfoKey]workloadInfo),\n\t\tinterceptWaiters:   make(map[string]*awaitIntercept),\n\t\tisPodDaemon:        cr.IsPodDaemon,\n\t\tsubnetViaWorkloads: cr.SubnetViaWorkloads,\n\t}\n\tsess.Context = withSession(sess.Context, sess)\n\treturn sess, nil\n}\n\nfunc (s *session) reconnectManager() (returnedErr error) {\n\tcfg := client.GetConfig(s)\n\ttos := cfg.Timeouts()\n\ttc, cancel := tos.TimeoutContext(s, client.TimeoutTrafficManagerConnect)\n\tdefer cancel()\n\n\tconn, managerName, managerVersion, err := s.ConnectToManager(tc, k8s.GetManagerNamespace(s))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\tif returnedErr != nil {\n\t\t\tconn.Close()\n\t\t}\n\t}()\n\n\t_, err = manager.NewManagerClient(conn).ReconnectClient(tc, &manager.ReconnectClientRequest{\n\t\tSession: s.sessionInfo,\n\t\tClient: &manager.ClientInfo{\n\t\t\tName:      s.clientID,\n\t\t\tNamespace: s.Namespace,\n\t\t\tInstallId: s.installID,\n\t\t\tProduct:   \"telepresence\",\n\t\t\tVersion:   client.Version(),\n\t\t},\n\t\tIntercepts: s.getCurrentInterceptInfos(),\n\t\tAgents:     s.getCurrentAgents(),\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to reconnect client: %w\", err)\n\t}\n\n\ts.managerConn = conn\n\ts.managerName = managerName\n\ts.managerVersion = managerVersion\n\treturn nil\n}\n\nfunc (s *session) remain() error {\n\tctx, cancel := client.GetConfig(s).Timeouts().TimeoutContext(s, client.TimeoutTrafficManagerAPI)\n\tdefer cancel()\n\tvar lastActivity *timestamppb.Timestamp\n\tln := atomic.LoadInt64(&s.lastActivity)\n\tif ln != 0 {\n\t\tlastActivity = timestamppb.New(time.Unix(0, ln))\n\t}\n\t_, err := s.ManagerClient().Remain(ctx, &manager.RemainRequest{\n\t\tSession:      s.SessionInfo(),\n\t\tLastActivity: lastActivity,\n\t})\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"error calling Remain: %v\", client.CheckTimeout(ctx, err))\n\t}\n\treturn nil\n}\n\n// updateDaemonNamespacesLocked will create a new DNS search path from the given namespaces and\n// send it to the DNS-resolver in the daemon.\nfunc (s *session) updateDaemonNamespaces() {\n\tconst svcDomain = \"svc\"\n\n\tdomains := s.GetCurrentNamespaces(false)\n\tif !slices.Contains(domains, svcDomain) {\n\t\tdomains = append(domains, svcDomain)\n\t}\n\tclog.Debugf(s, \"posting top-level domains %v to root daemon\", domains)\n\n\terr := s.WithRootClient(s, func(ctx context.Context, rd rootdRpc.DaemonClient) (err error) {\n\t\t_, err = rd.SetDNSTopLevelDomains(ctx, &rootdRpc.Domains{Domains: domains})\n\t\treturn err\n\t})\n\tif err != nil {\n\t\tclog.Errorf(s, \"error posting domains %v to root daemon: %v\", domains, err)\n\t}\n\tclog.Debug(s, \"domains posted successfully\")\n}\n\nfunc (s *session) startServices(g log.Group) {\n\tg.Go(\"remain\", s.remainLoop)\n\tg.Go(\"agents\", s.watchAgentsLoop)\n\tg.Go(\"intercept-port-forward\", s.watchInterceptsHandler)\n}\n\nfunc runWithRetry(ctx context.Context, f func(context.Context) error) error {\n\tbackoff := 100 * time.Millisecond\n\tfor ctx.Err() == nil {\n\t\tif err := f(ctx); err != nil {\n\t\t\tclog.Error(ctx, err)\n\t\t\ttime.Sleep(backoff)\n\t\t\tbackoff *= 2\n\t\t\tif backoff > 3*time.Second {\n\t\t\t\tbackoff = 3 * time.Second\n\t\t\t}\n\t\t} else {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *session) SessionInfo() *manager.SessionInfo {\n\treturn s.sessionInfo\n}\n\n// getInfosForWorkloads returns a list of workloads found in the given namespace that fulfils the given filter criteria.\nfunc (s *session) getInfosForWorkloads(\n\tnamespaces []string,\n\tiMap map[string][]*manager.InterceptInfo,\n\tgMap map[string][]*rpc.IngestInfo,\n\tsMap map[string]string,\n\tfilter rpc.ListRequest_Filter,\n) []*rpc.WorkloadInfo {\n\twiMap := make(map[string]*rpc.WorkloadInfo)\n\ts.eachWorkload(namespaces, func(wlKind manager.WorkloadInfo_Kind, name, namespace string, info workloadInfo) {\n\t\tkind := wlKind.String()\n\t\twlInfo := &rpc.WorkloadInfo{\n\t\t\tName:                 name,\n\t\t\tNamespace:            namespace,\n\t\t\tWorkloadResourceType: kind,\n\t\t\tUid:                  string(info.uid),\n\t\t}\n\t\tif info.state != workload.StateAvailable {\n\t\t\twlInfo.NotInterceptableReason = info.state.String()\n\t\t}\n\n\t\tvar ok bool\n\t\tfilterMatch := rpc.ListRequest_EVERYTHING\n\n\t\tfilterMatch &= ^(rpc.ListRequest_REPLACEMENTS | rpc.ListRequest_INTERCEPTS | rpc.ListRequest_WIRETAPS)\n\t\tiis, ok := iMap[name]\n\t\tif ok {\n\t\t\tfor _, ii := range iis {\n\t\t\t\tinclude := false\n\t\t\t\tswitch {\n\t\t\t\tcase ii.Spec.NoDefaultPort:\n\t\t\t\t\tfilterMatch |= rpc.ListRequest_REPLACEMENTS\n\t\t\t\t\tinclude = filter&rpc.ListRequest_REPLACEMENTS != 0\n\t\t\t\tcase ii.Spec.Wiretap:\n\t\t\t\t\tfilterMatch |= rpc.ListRequest_WIRETAPS\n\t\t\t\t\tinclude = filter&rpc.ListRequest_WIRETAPS != 0\n\t\t\t\tdefault:\n\t\t\t\t\tfilterMatch |= rpc.ListRequest_INTERCEPTS\n\t\t\t\t\tinclude = filter&rpc.ListRequest_INTERCEPTS != 0\n\t\t\t\t}\n\t\t\t\tif include || filter == 0 {\n\t\t\t\t\twlInfo.InterceptInfo = append(wlInfo.InterceptInfo, ii)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif wlInfo.IngestInfo, ok = gMap[name]; !ok {\n\t\t\tfilterMatch &= ^rpc.ListRequest_INGESTS\n\t\t}\n\t\tif wlInfo.AgentVersion, ok = sMap[name]; !ok {\n\t\t\tfilterMatch &= ^rpc.ListRequest_INSTALLED_AGENTS\n\t\t}\n\t\tclog.Debugf(s, \"filter %d, filterMatch %d\", filter, filterMatch)\n\t\tif filter != 0 && filter&filterMatch == 0 {\n\t\t\treturn\n\t\t}\n\t\twiMap[fmt.Sprintf(\"%s:%s.%s\", kind, name, namespace)] = wlInfo\n\t})\n\twiz := make([]*rpc.WorkloadInfo, len(wiMap))\n\ti := 0\n\tfor _, wi := range wiMap {\n\t\twiz[i] = wi\n\t\ti++\n\t}\n\tsort.Slice(wiz, func(i, j int) bool { return wiz[i].Name < wiz[j].Name })\n\treturn wiz\n}\n\nfunc (s *session) WatchWorkloads(wr *rpc.WatchWorkloadsRequest, stream userd.WatchWorkloadsStream) error {\n\tid := uuid.New()\n\tch := make(chan struct{})\n\ts.workloadsLock.Lock()\n\tif s.workloadSubscribers == nil {\n\t\ts.workloadSubscribers = make(map[uuid.UUID]chan struct{})\n\t}\n\ts.workloadSubscribers[id] = ch\n\ts.workloadsLock.Unlock()\n\n\tdefer func() {\n\t\ts.workloadsLock.Lock()\n\t\tdelete(s.workloadSubscribers, id)\n\t\ts.workloadsLock.Unlock()\n\t}()\n\n\tsend := func() error {\n\t\tws, err := s.WorkloadInfoSnapshot(wr.Namespaces, rpc.ListRequest_UNSPECIFIED)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn stream.Send(ws)\n\t}\n\n\t// Send initial snapshot\n\tif err := send(); err != nil {\n\t\treturn err\n\t}\n\tfor {\n\t\tselect {\n\t\tcase <-s.Done():\n\t\t\treturn nil\n\t\tcase <-ch:\n\t\t\tif err := send(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *session) ensureWatchers(namespaces []string) {\n\twg := sync.WaitGroup{}\n\twg.Add(len(namespaces))\n\tfor _, ns := range namespaces {\n\t\ts.workloadsLock.Lock()\n\t\t_, ok := s.workloads[ns]\n\t\ts.workloadsLock.Unlock()\n\t\tif ok {\n\t\t\twg.Done()\n\t\t} else {\n\t\t\tgo func() {\n\t\t\t\terr := s.workloadsWatcher(ns, &wg)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(s, \"error ensuring watcher for namespace %s: %v\", ns, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}()\n\t\t\tclog.Debugf(s, \"watcher for namespace %s started\", ns)\n\t\t}\n\t}\n\twg.Wait()\n}\n\nfunc (s *session) WorkloadInfoSnapshot(\n\tnamespaces []string,\n\tfilter rpc.ListRequest_Filter,\n) (*rpc.WorkloadInfoSnapshot, error) {\n\tis := s.getCurrentIntercepts()\n\n\tvar nss []string\n\tvar sMap map[string]string\n\tnss = make([]string, 0, len(namespaces))\n\tfor _, ns := range namespaces {\n\t\tns = s.ActualNamespace(ns)\n\t\tif ns != \"\" {\n\t\t\tnss = append(nss, ns)\n\t\t}\n\t}\n\tif len(nss) == 0 {\n\t\t// none of the namespaces are currently mapped\n\t\tclog.Debug(s, \"No namespaces are mapped\")\n\t\treturn &rpc.WorkloadInfoSnapshot{}, nil\n\t}\n\tif len(nss) == 1 && nss[0] == s.Namespace {\n\t\tcas := s.getCurrentAgents()\n\t\tsMap = make(map[string]string, len(cas))\n\t\tfor _, a := range cas {\n\t\t\tsMap[a.Name] = a.Version\n\t\t}\n\t}\n\ts.ensureWatchers(nss)\n\tiMap := make(map[string][]*manager.InterceptInfo, len(is))\nnextIs:\n\tfor _, i := range is {\n\t\tfor _, ns := range nss {\n\t\t\tif i.Spec.Namespace == ns {\n\t\t\t\tiMap[i.Spec.Agent] = append(iMap[i.Spec.Agent], i.InterceptInfo)\n\t\t\t\tcontinue nextIs\n\t\t\t}\n\t\t}\n\t}\n\tgMap := make(map[string][]*rpc.IngestInfo, s.currentIngests.Size())\n\ts.currentIngests.Range(func(key ingestKey, ig *ingest) bool {\n\t\tgMap[key.workload] = append(gMap[key.workload], ig.response())\n\t\treturn true\n\t})\n\n\tworkloadInfos := s.getInfosForWorkloads(nss, iMap, gMap, sMap, filter)\n\treturn &rpc.WorkloadInfoSnapshot{Workloads: workloadInfos}, nil\n}\n\nfunc (s *session) remainLoop(_ context.Context) error {\n\tticker := time.NewTicker(client.GetConfig(s).Grpc().PingInterval)\n\tdefer func() {\n\t\tticker.Stop()\n\t\tc, cancel := context.WithTimeout(context.WithoutCancel(s), 3*time.Second)\n\t\tdefer cancel()\n\t\tif _, err := s.ManagerClient().Depart(c, s.SessionInfo()); err != nil {\n\t\t\tclog.Errorf(c, \"failed to depart from manager: %v\", err)\n\t\t} else {\n\t\t\t// Depart succeeded, so the traffic-manager has dropped the session. We should too\n\t\t\tif err = deleteSessionInfoFromUserCache(c, s.daemonID); err != nil {\n\t\t\t\tclog.Errorf(c, \"failed to delete session from user cache: %v\", err)\n\t\t\t}\n\t\t}\n\t\t// Call Close() in separate go-routine because it might block.\n\t\tgo s.managerConn.Close()\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase <-s.Done():\n\t\t\treturn nil\n\t\tcase <-ticker.C:\n\t\t\tif err := s.remain(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n}\n\n// CheckStatus checks that the given ConnectRequest is aligned with the current status of the session.\n// If the request is not aligned, it returns a ConnectInfo with the error code set to MUST_RESTART.\n// If the request is aligned, it returns nil.\nfunc (s *session) CheckStatus(cr *rpc.ConnectRequest) error {\n\tconfig, err := k8s.DaemonKubeconfig(s, cr)\n\tif err != nil {\n\t\t// Treat an incomplete request as OK. It stems from an implicit\n\t\t// connect that doesn't define a kubeconfig at all.\n\t\tconfig = s.Kubeconfig\n\t}\n\tif len(cr.MappedNamespaces) == 1 && cr.MappedNamespaces[0] == \"all\" {\n\t\tcr.MappedNamespaces = nil\n\t}\n\t// If namespaces are specified in the request, then we must ensure that they are the same as the current ones\n\t// because the request takes precedence over namespaces configured in the client configuration or by the traffic-manager.\n\tif len(cr.MappedNamespaces) == 0 || slices.Equal(cr.MappedNamespaces, s.MappedNamespaces) {\n\t\tenvEQ := true\n\t\tfor k, v := range cr.Environment {\n\t\t\tif k[0] == '-' {\n\t\t\t\tif _, ok := os.LookupEnv(k[:1]); ok {\n\t\t\t\t\tenvEQ = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif ov, ok := os.LookupEnv(k); !ok || ov != v {\n\t\t\t\t\tenvEQ = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif envEQ && s.ContextServiceAndFlagsEqual(config) && s.subnetViaWorkloadsEqual(cr.SubnetViaWorkloads) {\n\t\t\treturn nil\n\t\t}\n\t}\n\treturn errcat.User.New(\"Cluster configuration changed, please quit telepresence and reconnect\")\n}\n\nfunc (s *session) subnetViaWorkloadsEqual(workloads []*rootdRpc.SubnetViaWorkload) bool {\n\treturn slices.EqualFunc(s.subnetViaWorkloads, workloads, func(a, b *rootdRpc.SubnetViaWorkload) bool {\n\t\treturn proto.Equal(a, b)\n\t})\n}\n\n// UpdateStatus checks if the given ConnectRequest matches the current status of the session. If it does, then the status of the session is updated\n// to match the current client configuration and the traffic-manager.\n// If the request is not aligned with the current status of the session, it returns a ConnectInfo with the error code set to MUST_RESTART.\n// If the request is aligned, it returns the current status.\nfunc (s *session) UpdateStatus(ctx context.Context, cr *rpc.ConnectRequest) (*rpc.ConnectInfo, error) {\n\terr := s.CheckStatus(cr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.updateClientConfig(ctx, cr.MappedNamespaces)\n\treturn s.Status(ctx)\n}\n\nfunc (s *session) Status(ctx context.Context) (*rpc.ConnectInfo, error) {\n\treturn s.status(ctx, false)\n}\n\nfunc (s *session) updateClientConfig(ctx context.Context, namespaces []string) {\n\tvar tmCfg client.Config\n\tcliCfg, err := s.ManagerClient().GetClientConfig(ctx, &empty.Empty{})\n\tif err != nil {\n\t\tif status.Code(err) != codes.Unimplemented {\n\t\t\tclog.Warnf(s, \"Failed to get remote config from traffic manager: %v\", err)\n\t\t}\n\t} else {\n\t\ttmCfg, err = client.ParseConfigYAML(ctx, \"client configuration from cluster\", cliCfg.ConfigYaml)\n\t\tif err != nil {\n\t\t\tclog.Warn(s, err.Error())\n\t\t}\n\t}\n\n\t// Merge traffic-manager's reported config, but get priority to the local config.\n\tcfg := client.GetConfig(s)\n\tif tmCfg != nil {\n\t\ttmMappedNamespaces := tmCfg.Cluster().MappedNamespaces\n\t\tclientMappedNamespaces := cfg.Cluster().MappedNamespaces\n\t\tcfg = tmCfg.Merge(cfg)\n\n\t\t// We do not want to override the local config with the traffic-manager's config even if the local config is empty.\n\t\tcfg.Cluster().MappedNamespaces = clientMappedNamespaces\n\t\tswitch {\n\t\tcase len(namespaces) > 0:\n\t\t\t// Use the namespaces specified by the user.\n\t\tcase len(clientMappedNamespaces) > 0:\n\t\t\t// Use the namespaces specified by the client configuration.\n\t\t\tnamespaces = clientMappedNamespaces\n\t\tcase len(tmMappedNamespaces) > 0:\n\t\t\t// Use the namespaces specified by the traffic-manager.\n\t\t\tnamespaces = tmMappedNamespaces\n\t\t}\n\t\tif s.SetMappedNamespaces(namespaces) {\n\t\t\tif len(namespaces) == 0 {\n\t\t\t\tif k8sapi.CanWatchNamespaces(s) {\n\t\t\t\t\tclog.Infof(s, \"Will watch all namespaces\")\n\t\t\t\t\ts.StartNamespaceWatcher()\n\t\t\t\t} else {\n\t\t\t\t\tclog.Warnf(s, \"Unable to watch all namespaces\")\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tclog.Infof(s, \"Will use mapped namespaces %s\", namespaces)\n\t\t\t}\n\t\t}\n\t\trt := cfg.Routing()\n\t\trt.NeverProxy = subnet.Unique(append(rt.NeverProxy, tmCfg.Routing().NeverProxy...))\n\t\tclient.ReplaceConfig(s, cfg)\n\t}\n\tif clog.Enabled(s, slog.LevelDebug) {\n\t\tclog.Debug(s, \"Client configuration\")\n\t\tbuf, _ := json.Marshal(cfg)\n\t\tbuf, _ = yaml.JSONToYAML(buf)\n\t\tsc := bufio.NewScanner(bytes.NewReader(buf))\n\t\tfor sc.Scan() {\n\t\t\tclog.Debug(s, sc.Text())\n\t\t}\n\t}\n}\n\nfunc (s *session) status(ctx context.Context, initial bool) (*rpc.ConnectInfo, error) {\n\tcfg := s.Kubeconfig\n\tret := &rpc.ConnectInfo{\n\t\tInitial:          initial,\n\t\tClusterContext:   cfg.KubeContext,\n\t\tClusterServer:    cfg.Server,\n\t\tManagerInstallId: s.GetManagerInstallId(),\n\t\tSessionInfo:      s.SessionInfo(),\n\t\tConnectionName:   s.daemonID.Name,\n\t\tKubeFlags:        s.OriginalFlagMap,\n\t\tNamespace:        s.Namespace,\n\t\tIngests:          s.getCurrentIngests(),\n\t\tIntercepts:       &manager.InterceptInfoSnapshot{Intercepts: s.getCurrentInterceptInfos()},\n\t\tManagerVersion: &manager.VersionInfo2{\n\t\t\tName:    s.managerName,\n\t\t\tVersion: \"v\" + s.managerVersion.String(),\n\t\t},\n\t\tManagerNamespace:   k8s.GetManagerNamespace(s),\n\t\tSubnetViaWorkloads: s.subnetViaWorkloads,\n\t\tVersion:            client.VersionInfo(s),\n\t}\n\tif len(s.MappedNamespaces) > 0 || len(client.GetConfig(s).Cluster().MappedNamespaces) > 0 {\n\t\tret.MappedNamespaces = s.GetCurrentNamespaces(true)\n\t}\n\terr := s.WithRootClient(ctx, func(ctx context.Context, rd rootdRpc.DaemonClient) (err error) {\n\t\tret.DaemonStatus, err = rd.Status(s, &empty.Empty{})\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn ret, nil\n}\n\n// Uninstall one or all traffic-agents from the cluster if the client has sufficient credentials to do so.\n//\n// Uninstalling all or specific agents require that the client can get and update the agents ConfigMap.\nfunc (s *session) Uninstall(ctx context.Context, ur *rpc.UninstallRequest) error {\n\t_, err := s.ManagerClient().UninstallAgents(ctx, &manager.UninstallAgentsRequest{\n\t\tSessionInfo: s.sessionInfo,\n\t\tAgents:      ur.Agents,\n\t})\n\treturn err\n}\n\nfunc (s *session) getNetworkInfo(cr *rpc.ConnectRequest) *rootdRpc.NetworkConfig {\n\tcfg := client.GetConfig(s)\n\tjsonCfg, _ := json.Marshal(cfg)\n\treturn &rootdRpc.NetworkConfig{\n\t\tKubeFlags:          cr.KubeFlags,\n\t\tKubeconfigData:     cr.KubeconfigData,\n\t\tNamespace:          s.Namespace,\n\t\tManagerNamespace:   k8s.GetManagerNamespace(s),\n\t\tMappedNamespaces:   s.GetCurrentNamespaces(true),\n\t\tSession:            s.sessionInfo,\n\t\tSubnetViaWorkloads: s.subnetViaWorkloads,\n\t\tHomeDir:            homedir.HomeDir(),\n\t\tClientConfig:       jsonCfg,\n\t}\n}\n\nfunc (s *session) connectRootDaemon(timeoutCtx context.Context, nc *rootdRpc.NetworkConfig, wg *sync.WaitGroup, isPodDaemon bool) (rd rootdRpc.DaemonClient, err error) {\n\t// establish a connection to the root daemon gRPC grpcService\n\tclog.Info(s, \"Connecting to root daemon...\")\n\tsvc := s.GetService()\n\tif svc.RootSessionInProcess() {\n\t\t// Just run the root session in-process.\n\t\tactivity := make(chan time.Time)\n\t\trootSession, err := rootd.NewInProcSession(s.Cluster, nc, s.managerConn, s.managerVersion, activity, isPodDaemon)\n\t\tif err != nil {\n\t\t\tclose(activity)\n\t\t\treturn nil, err\n\t\t}\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tselect {\n\t\t\t\tcase <-s.Done():\n\t\t\t\t\tclose(activity)\n\t\t\t\t\treturn\n\t\t\t\tcase ats, ok := <-activity:\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\tclog.Debugf(s, \"root session last activity: %v\", ats)\n\t\t\t\t\tatomic.StoreInt64(&s.lastActivity, ats.UnixNano())\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tg := log.NewGroup(rootSession)\n\t\tif err = rootSession.Start(g, svc.TeleroutePort()); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trd = rootSession\n\n\t\t// Give in-proc root session services a chance to clean up.\n\t\twg.Go(func() {\n\t\t\terr := g.Wait()\n\t\t\tif err != nil && !errors.Is(err, context.Canceled) {\n\t\t\t\tclog.Errorf(s, \"root session exited with error: %v\", err)\n\t\t\t}\n\t\t})\n\t} else {\n\t\tvar conn *grpc.ClientConn\n\t\tconn, err = daemon.DialRootDaemon(timeoutCtx, true)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer func() {\n\t\t\tif err != nil {\n\t\t\t\tconn.Close()\n\t\t\t}\n\t\t}()\n\t\trd = rootdRpc.NewDaemonClient(conn)\n\n\t\tfor attempt := 1; ; attempt++ {\n\t\t\tvar rootStatus *rootdRpc.DaemonStatus\n\t\t\trootStatus, err = rd.Connect(timeoutCtx, nc)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to connect to root daemon: %w\", err)\n\t\t\t}\n\t\t\toc := rootStatus.OutboundConfig\n\t\t\tif oc == nil || oc.Session == nil {\n\t\t\t\t// This is an internal error. Something is wrong with the root daemon.\n\t\t\t\treturn nil, errors.New(\"root daemon's OutboundConfig has no session\")\n\t\t\t}\n\t\t\tif oc.Session.SessionId == nc.Session.SessionId {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Root daemon was running an old session. This indicates that this daemon somehow\n\t\t\t// crashed without disconnecting. So let's do that now, and then reconnect...\n\t\t\tif attempt == 2 {\n\t\t\t\t// ...or not, since we've already done it.\n\t\t\t\treturn nil, errors.New(\"unable to reconnect to root daemon\")\n\t\t\t}\n\t\t\tif _, err = rd.Disconnect(s, &empty.Empty{}); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to disconnect from the root daemon: %w\", err)\n\t\t\t}\n\t\t}\n\t\taw, err := rd.ActivityWatcher(s, &empty.Empty{})\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get activity watcher: %w\", err)\n\t\t}\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tat, err := aw.Recv()\n\t\t\t\tif err != nil {\n\t\t\t\t\tif !errors.Is(err, context.Canceled) {\n\t\t\t\t\t\tclog.Errorf(s, \"activity watcher failed: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tats := at.Activity.AsTime()\n\t\t\t\tclog.Debugf(s, \"root session last activity: %v\", ats)\n\t\t\t\tatomic.StoreInt64(&s.lastActivity, ats.UnixNano())\n\t\t\t}\n\t\t}()\n\t}\n\n\t// The root daemon needs time to set up the TUN-device and DNS, which involves interacting\n\t// with the cluster-side traffic-manager.\n\tif _, err = rd.WaitForNetwork(timeoutCtx, &empty.Empty{}); err != nil {\n\t\tif se, ok := status.FromError(err); ok {\n\t\t\terr = se.Err()\n\t\t}\n\t\treturn nil, fmt.Errorf(\"failed to connect to root daemon: %v\", err)\n\t}\n\tclog.Debug(s, \"Connected to root daemon\")\n\treturn rd, nil\n}\n\nfunc (s *session) eachWorkload(namespaces []string, do func(kind manager.WorkloadInfo_Kind, name, namespace string, info workloadInfo)) {\n\ts.workloadsLock.Lock()\n\tfor _, ns := range namespaces {\n\t\tif workloads, ok := s.workloads[ns]; ok {\n\t\t\tfor key, info := range workloads {\n\t\t\t\tdo(key.kind, key.name, ns, info)\n\t\t\t}\n\t\t}\n\t}\n\ts.workloadsLock.Unlock()\n}\n\nfunc (s *session) RerouteLocalPort(ap types.AddrPortProto, srcPort uint16) {\n\tfw := forwarder.New(types.PortAndProto{\n\t\tPort:  srcPort,\n\t\tProto: ap.Proto,\n\t}, tunnel.ClientToAgent, ap.AddrPort)\n\n\tgo func() {\n\t\tctx := clog.WithGroup(s, fmt.Sprintf(\"%d=>%s\", srcPort, ap))\n\t\terr := fw.Serve(ctx, nil)\n\t\tif err != nil && ctx.Err() == nil {\n\t\t\tclog.Errorf(ctx, \"port-forwarder failed with %v\", err)\n\t\t}\n\t}()\n}\n\nfunc (s *session) workloadsWatcher(namespace string, synced *sync.WaitGroup) error {\n\tdefer func() {\n\t\tif synced != nil {\n\t\t\tsynced.Done()\n\t\t}\n\t}()\n\treturn watcher.WatchWithRetry(s, \"WatchWorkloads\", client.GetConfig(s).Grpc().WatchRetryInterval,\n\t\tfunc(ctx context.Context) (grpc.ServerStreamingClient[manager.WorkloadEventsDelta], error) {\n\t\t\treturn s.ManagerClient().WatchWorkloads(ctx, &manager.WorkloadEventsRequest{SessionInfo: s.sessionInfo, Namespace: namespace})\n\t\t},\n\t\tfunc(wls *manager.WorkloadEventsDelta) error {\n\t\t\ts.workloadsLock.Lock()\n\t\t\tworkloads, ok := s.workloads[namespace]\n\t\t\tif !ok {\n\t\t\t\tworkloads = make(map[workloadInfoKey]workloadInfo)\n\t\t\t\ts.workloads[namespace] = workloads\n\t\t\t}\n\n\t\t\tfor _, we := range wls.GetEvents() {\n\t\t\t\tw := we.Workload\n\t\t\t\tkey := workloadInfoKey{kind: w.Kind, name: w.Name}\n\t\t\t\tif we.Type == manager.WorkloadEvent_DELETED {\n\t\t\t\t\tclog.Debugf(s, \"Deleting workload %s/%s.%s\", key.kind, key.name, namespace)\n\t\t\t\t\tdelete(workloads, key)\n\t\t\t\t} else {\n\t\t\t\t\tvar clients []string\n\t\t\t\t\tif lc := len(w.InterceptClients); lc > 0 {\n\t\t\t\t\t\tclients = make([]string, lc)\n\t\t\t\t\t\tfor i, ic := range w.InterceptClients {\n\t\t\t\t\t\t\tclients[i] = ic.Client\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tstate := workload.StateFromRPC(w.State)\n\t\t\t\t\tclog.Debugf(s, \"Adding workload %s/%s.%s %s %s %s\", key.kind, key.name, namespace, state, w.AgentState, clients)\n\t\t\t\t\tworkloads[key] = workloadInfo{\n\t\t\t\t\t\tuid:              k8sTypes.UID(w.Uid),\n\t\t\t\t\t\tstate:            state,\n\t\t\t\t\t\tagentState:       w.AgentState,\n\t\t\t\t\t\tinterceptClients: clients,\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, subscriber := range s.workloadSubscribers {\n\t\t\t\tselect {\n\t\t\t\tcase subscriber <- struct{}{}:\n\t\t\t\tdefault:\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.workloadsLock.Unlock()\n\t\t\tif synced != nil {\n\t\t\t\tsynced.Done()\n\t\t\t\tsynced = nil\n\t\t\t}\n\t\t\treturn nil\n\t\t}, nil)\n}\n"
  },
  {
    "path": "pkg/client/userd/trafficmgr/session_info_cache.go",
    "content": "package trafficmgr\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"path/filepath\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cache\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client/cli/daemon\"\n)\n\nfunc sessionInfoFile(daemonID *daemon.Identifier) string {\n\treturn filepath.Join(\"sessions\", daemonID.InfoFileName())\n}\n\ntype savedSession struct {\n\tKubeContext string               `json:\"kubeContext\"`\n\tNamespace   string               `json:\"namespace\"`\n\tSession     *manager.SessionInfo `json:\"session\"`\n}\n\n// LoadSessionInfoFromUserCache gets the SessionInfo from cache or returns an error if something goes\n// wrong while loading or unmarshalling.\nfunc LoadSessionInfoFromUserCache(ctx context.Context, daemonID *daemon.Identifier) (*manager.SessionInfo, error) {\n\tvar ss *savedSession\n\terr := cache.LoadFromUserCache(ctx, &ss, sessionInfoFile(daemonID))\n\tif err == nil && ss.KubeContext == daemonID.KubeContext && ss.Namespace == daemonID.Namespace {\n\t\treturn ss.Session, nil\n\t}\n\tif err != nil && errors.Is(err, fs.ErrNotExist) {\n\t\terr = nil\n\t}\n\treturn nil, err\n}\n\n// saveSessionInfoToUserCache saves the provided SessionInfo to user cache and returns an error if\n// something goes wrong while marshalling or persisting.\nfunc saveSessionInfoToUserCache(ctx context.Context, daemonID *daemon.Identifier, session *manager.SessionInfo) error {\n\treturn cache.SaveToUserCache(ctx, &savedSession{\n\t\tKubeContext: daemonID.KubeContext,\n\t\tNamespace:   daemonID.Namespace,\n\t\tSession:     session,\n\t}, sessionInfoFile(daemonID), cache.Public)\n}\n\n// deleteSessionInfoFromUserCache removes SessionInfo cache if existing or returns an error. An attempt\n// to remove a non-existing cache is a no-op and the function returns nil.\nfunc deleteSessionInfoFromUserCache(ctx context.Context, daemonID *daemon.Identifier) error {\n\treturn cache.DeleteFromUserCache(ctx, sessionInfoFile(daemonID))\n}\n"
  },
  {
    "path": "pkg/client/version.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/blang/semver/v4\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/version\"\n)\n\nvar DisplayName = \"Telepresence\" //nolint:gochecknoglobals // extension point\n\n// Version returns the version of this executable.\nfunc Version() string {\n\treturn version.Version\n}\n\nfunc Semver() semver.Version {\n\treturn version.Structured\n}\n\nfunc Executable() (string, error) {\n\treturn version.GetExecutable()\n}\n\nfunc VersionInfo(ctx context.Context) *common.VersionInfo {\n\treturn &common.VersionInfo{\n\t\tApiVersion: APIVersion,\n\t\tVersion:    Version(),\n\t\tExecutable: GetExe(ctx),\n\t\tName:       DisplayName,\n\t}\n}\n\n// GetInstallMechanism returns how the executable was installed on the machine.\nfunc GetInstallMechanism() (string, error) {\n\texecPath, err := os.Executable()\n\tmechanism := \"undetermined\"\n\tif err != nil {\n\t\twrapErr := fmt.Errorf(\"unable to get exec path: %w\", err)\n\t\treturn mechanism, wrapErr\n\t}\n\n\treturn GetMechanismFromPath(execPath)\n}\n\n// GetMechanismFromPath is a helper function that contains most of the logic\n// required for GetInstallMechanism, but enables us to test it since we can\n// control the path passed in.\nfunc GetMechanismFromPath(execPath string) (string, error) {\n\t// Some package managers, like brew, symlink binaries into /usr/local/bin.\n\t// We want to use the actual location of the executable when reporting metrics\n\t// so we follow the symlink to get the actual binary path\n\tmechanism := \"undetermined\"\n\tbinaryPath, err := filepath.EvalSymlinks(execPath)\n\tif err != nil {\n\t\twrapErr := fmt.Errorf(\"error following executable symlink %s: %w\", execPath, err)\n\t\treturn mechanism, wrapErr\n\t}\n\tswitch {\n\tcase runtime.GOOS == \"darwin\" && strings.Contains(binaryPath, \"Cellar\"):\n\t\tmechanism = \"brew\"\n\tcase strings.Contains(binaryPath, \"docker\"):\n\t\tmechanism = \"docker\"\n\tdefault:\n\t\tmechanism = \"website\"\n\t}\n\treturn mechanism, nil\n}\n"
  },
  {
    "path": "pkg/client/version_test.go",
    "content": "package client_test\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n)\n\nfunc TestGetInstallMechanism(t *testing.T) {\n\ttype testcase struct {\n\t\tbinaryPath               string\n\t\tsymLinkPath              string\n\t\texpectedInstallMechanism string\n\t\terrFile                  bool\n\t}\n\tfakeExecDir := t.TempDir()\n\ttestcases := map[string]testcase{\n\t\t\"website-install\": {\n\t\t\tbinaryPath:               \"telepresence\",\n\t\t\tsymLinkPath:              \"\",\n\t\t\texpectedInstallMechanism: \"website\",\n\t\t\terrFile:                  false,\n\t\t},\n\t\t\"docker-install\": {\n\t\t\tbinaryPath:               \"docker/telepresence\",\n\t\t\tsymLinkPath:              \"\",\n\t\t\texpectedInstallMechanism: \"docker\",\n\t\t\terrFile:                  false,\n\t\t},\n\t\t\"docker-install-sym\": {\n\t\t\tbinaryPath:               \"docker/telepresence\",\n\t\t\tsymLinkPath:              \"telepresence\",\n\t\t\texpectedInstallMechanism: \"docker\",\n\t\t\terrFile:                  false,\n\t\t},\n\t\t// we care about the underlying executable so even if someone\n\t\t// symlinks to make it *seem* like it's installed via docker\n\t\t// it will use the actual executable.\n\t\t\"pseudo-symlink\": {\n\t\t\tbinaryPath:               \"telepresence\",\n\t\t\tsymLinkPath:              \"docker/telepresence\",\n\t\t\texpectedInstallMechanism: \"website\",\n\t\t\terrFile:                  false,\n\t\t},\n\t\t\"fail-executable-files\": {\n\t\t\tbinaryPath:               \"telepresence\",\n\t\t\tsymLinkPath:              \"\",\n\t\t\texpectedInstallMechanism: \"undetermined\",\n\t\t\terrFile:                  true,\n\t\t},\n\t}\n\tif runtime.GOOS == \"darwin\" {\n\t\ttestcases[\"brew-install\"] = testcase{\n\t\t\tbinaryPath:               \"Cellar/telepresence\",\n\t\t\tsymLinkPath:              \"\",\n\t\t\texpectedInstallMechanism: \"brew\",\n\t\t\terrFile:                  false,\n\t\t}\n\t\ttestcases[\"brew-install-sym\"] = testcase{\n\t\t\tbinaryPath:               \"Cellar/telepresence\",\n\t\t\tsymLinkPath:              \"telepresence\",\n\t\t\texpectedInstallMechanism: \"brew\",\n\t\t\terrFile:                  false,\n\t\t}\n\t} else {\n\t\t// we *only* support brew for macOS so we should report website in\n\t\t// this case.\n\t\ttestcases[\"brew-install\"] = testcase{\n\t\t\tbinaryPath:               \"Cellar/telepresence\",\n\t\t\tsymLinkPath:              \"\",\n\t\t\texpectedInstallMechanism: \"website\",\n\t\t\terrFile:                  false,\n\t\t}\n\t\ttestcases[\"brew-install-sym\"] = testcase{\n\t\t\tbinaryPath:               \"Cellar/telepresence\",\n\t\t\tsymLinkPath:              \"telepresence\",\n\t\t\texpectedInstallMechanism: \"website\",\n\t\t\terrFile:                  false,\n\t\t}\n\t}\n\n\tcreateFile := func(fullFilePath string) error {\n\t\terr := os.MkdirAll(filepath.Dir(fullFilePath), os.ModePerm)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tf, err := os.Create(fullFilePath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := f.Close(); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\tfor tcName, tcData := range testcases {\n\t\tt.Run(tcName, func(t *testing.T) {\n\t\t\t// Create the fake binary for our test\n\t\t\t// We include the tcName in the filePath, so we don't have to worry about\n\t\t\t// named collisions or cleaning up after each test\n\t\t\tfilePath := fmt.Sprintf(\"%s/%s/%s\", fakeExecDir, tcName, tcData.binaryPath)\n\t\t\tassert.NoError(t, createFile(filePath))\n\n\t\t\t// Create symlink if specified\n\t\t\tif tcData.symLinkPath != \"\" && tcData.errFile {\n\t\t\t\tt.Fatalf(\"symLinkPath and errFile are mutually exclusive\")\n\t\t\t}\n\t\t\tif tcData.symLinkPath != \"\" {\n\t\t\t\tsymLinkFile := fmt.Sprintf(\"%s/%s/%s\", fakeExecDir, tcName, tcData.symLinkPath)\n\t\t\t\tassert.NoError(t, os.MkdirAll(filepath.Dir(symLinkFile), os.ModePerm))\n\t\t\t\tassert.NoError(t, os.Symlink(filePath, symLinkFile))\n\t\t\t\tfilePath = symLinkFile\n\t\t\t}\n\n\t\t\tif tcData.errFile {\n\t\t\t\tfilePath = \"/not/a/real/file\"\n\t\t\t}\n\t\t\t// Validate the install mechanism is what we expect\n\t\t\tinstallMech, err := client.GetMechanismFromPath(filePath)\n\t\t\tassert.Equal(t, tcData.expectedInstallMechanism, installMech)\n\t\t\tif tcData.errFile {\n\t\t\t\tassert.Error(t, err)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/dnsproxy/lookup.go",
    "content": "package dnsproxy\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/miekg/dns\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nconst dnsTTL = 4\n\nconst (\n\tarpaV4 = \".in-addr.arpa.\"\n\tarpaV6 = \".ip6.arpa.\"\n)\n\ntype RRs []dns.RR\n\nfunc SupportedType(qType uint16) bool {\n\tswitch qType {\n\tcase dns.TypeA, dns.TypeAAAA, dns.TypePTR, dns.TypeCNAME, dns.TypeMX, dns.TypeNS, dns.TypeSRV, dns.TypeTXT:\n\t\treturn true\n\tdefault:\n\t\treturn false\n\t}\n}\n\nfunc writeRR(rr dns.RR, bf *strings.Builder) {\n\tswitch rr := rr.(type) {\n\tcase *dns.A:\n\t\tbf.WriteString(rr.A.String())\n\tcase *dns.AAAA:\n\t\tbf.WriteString(rr.AAAA.String())\n\tcase *dns.PTR:\n\t\tbf.WriteString(rr.Ptr)\n\tcase *dns.CNAME:\n\t\tbf.WriteString(rr.Target)\n\tcase *dns.MX:\n\t\tfmt.Fprintf(bf, \"%s(pref %d)\", rr.Mx, rr.Preference)\n\tcase *dns.NS:\n\t\tbf.WriteString(rr.Ns)\n\tcase *dns.SRV:\n\t\tfmt.Fprintf(bf, \"%s(port %d, prio %d, weight %d)\", rr.Target, rr.Port, rr.Priority, rr.Weight)\n\tcase *dns.TXT:\n\t\tbf.WriteString(strings.Join(rr.Txt, \",\"))\n\tdefault:\n\t\tbf.WriteString(rr.String())\n\t}\n}\n\nfunc (a RRs) PruneEmptyNames() RRs {\n\treturn slices.DeleteFunc(a, IsEmptyName)\n}\n\nfunc IsEmptyName(rr dns.RR) bool {\n\tswitch rr := rr.(type) {\n\tcase *dns.A:\n\t\treturn len(rr.A) == 0\n\tcase *dns.AAAA:\n\t\treturn len(rr.AAAA) == 0\n\t}\n\treturn false\n}\n\nfunc (a RRs) String() string {\n\tif len(a) == 0 {\n\t\treturn \"EMPTY\"\n\t}\n\tbf := strings.Builder{}\n\tbf.WriteByte('[')\n\tfor i, rr := range a {\n\t\tif i > 0 {\n\t\t\tbf.WriteByte(',')\n\t\t}\n\t\twriteRR(rr, &bf)\n\t}\n\tbf.WriteByte(']')\n\treturn bf.String()\n}\n\nfunc nibbleToInt(v string) (uint8, bool) {\n\tif len(v) != 1 {\n\t\treturn 0, false\n\t}\n\thd := v[0]\n\tif hd >= '0' && hd <= '9' {\n\t\treturn hd - '0', true\n\t}\n\tif hd >= 'A' && hd <= 'F' {\n\t\treturn 10 + hd - 'A', true\n\t}\n\tif hd >= 'a' && hd <= 'f' {\n\t\treturn 10 + hd - 'a', true\n\t}\n\treturn 0, false\n}\n\nfunc PtrAddress(addr string) (netip.Addr, error) {\n\ta, err := netip.ParseAddr(addr)\n\tswitch {\n\tcase err == nil:\n\t\treturn a, nil\n\tcase strings.HasSuffix(addr, arpaV4):\n\t\tix := addr[0 : len(addr)-len(arpaV4)]\n\t\tif a, err := netip.ParseAddr(ix); err == nil && a.Is4() {\n\t\t\tip := a.As4()\n\t\t\tip0 := ip[0]\n\t\t\tip1 := ip[1]\n\t\t\tip[0] = ip[3]\n\t\t\tip[1] = ip[2]\n\t\t\tip[2] = ip1\n\t\t\tip[3] = ip0\n\t\t\treturn netip.AddrFrom4(ip), nil\n\t\t}\n\t\treturn a, fmt.Errorf(\"%q is not a valid IP (v4) prefixing .in-addr.arpa\", ix)\n\tcase strings.HasSuffix(addr, arpaV6):\n\t\thds := strings.Split(addr[0:len(addr)-len(arpaV6)], \".\")\n\t\tif len(hds) != 32 {\n\t\t\treturn a, errors.New(\"expected 32 nibbles to prefix .ip6.arpa\")\n\t\t}\n\t\tip := [16]byte{}\n\t\todd := false\n\t\tfor i, nb := range hds {\n\t\t\td, ok := nibbleToInt(nb)\n\t\t\tif !ok {\n\t\t\t\treturn a, errors.New(\"expected 32 nibbles to prefix .ip6.arpa\")\n\t\t\t}\n\t\t\tb := 15 - i>>1\n\t\t\tif odd {\n\t\t\t\tip[b] |= d << 4\n\t\t\t} else {\n\t\t\t\tip[b] = d\n\t\t\t}\n\t\t\todd = !odd\n\t\t}\n\t\treturn netip.AddrFrom16(ip), nil\n\tdefault:\n\t\treturn a, fmt.Errorf(\"%q is neither a valid IP-address or a valid reverse notation\", addr)\n\t}\n}\n\nfunc NewHeader(qName string, qType uint16) dns.RR_Header {\n\treturn dns.RR_Header{Name: qName, Rrtype: qType, Class: dns.ClassINET, Ttl: dnsTTL}\n}\n\n// useLookupName takes care of an undocumented \"feature\" in some lookup functions.\n// If the name ends with a dot, then no search path will be applied. If, however,\n// the name doesn't end with a dot, the search path is always applied, and the name\n// is never used verbatim.\n// A name ending with noSearchDomain will always be considered final.\nfunc useLookupName(qName, noSearchDomain string) (string, bool) {\n\tif noSearchDomain != \"\" && strings.HasSuffix(qName, noSearchDomain) {\n\t\treturn qName, true\n\t}\n\tdots := 0\n\tname := qName[:len(qName)-1]\n\tfor _, c := range name {\n\t\tif c == '.' {\n\t\t\tdots++\n\t\t}\n\t}\n\t// singleton name, it's safe to assume that a search path must be applied\n\t// With > 3 dots, we can safely assume that no search path should be applied\n\treturn name, dots == 0 || dots > 3\n}\n\nfunc lookupIP(ctx context.Context, network, qName, noSearchDomain string, r *net.Resolver) ([]net.IP, error) {\n\tname, final := useLookupName(qName, noSearchDomain)\n\tips, err := r.LookupIP(ctx, network, name)\n\tif err != nil && !final {\n\t\tclog.Errorf(ctx, \"LookupIP failed %q failed, trying LookupIP %q\", name, qName)\n\t\tips, err = r.LookupIP(ctx, network, qName)\n\t}\n\tif err == nil && len(ips) == 0 {\n\t\terr = &net.DNSError{\n\t\t\tErr:        \"no such host\",\n\t\t\tName:       name,\n\t\t\tIsNotFound: true,\n\t\t}\n\t}\n\treturn ips, err\n}\n\nfunc MakeDNSError(err error) (int, error) {\n\tvar dnsErr *net.DNSError\n\tswitch {\n\tcase errors.Is(err, context.DeadlineExceeded):\n\t\treturn dns.RcodeNameError, status.Error(codes.DeadlineExceeded, err.Error())\n\tcase errors.Is(err, context.Canceled):\n\t\treturn dns.RcodeNameError, status.Error(codes.Canceled, err.Error())\n\tcase errors.As(err, &dnsErr):\n\t\tswitch {\n\t\tcase dnsErr.IsNotFound:\n\t\t\treturn dns.RcodeNameError, nil\n\t\tcase dnsErr.IsTemporary:\n\t\t\treturn dns.RcodeNameError, status.Error(codes.Unavailable, dnsErr.Error())\n\t\tcase dnsErr.IsTimeout:\n\t\t\treturn dns.RcodeNameError, status.Error(codes.DeadlineExceeded, dnsErr.Error())\n\t\t}\n\t}\n\treturn dns.RcodeServerFailure, status.Error(codes.Internal, err.Error())\n}\n\nfunc makeError(err error) (RRs, int, error) {\n\trCode, err := MakeDNSError(err)\n\treturn nil, rCode, err\n}\n\n//nolint:cyclop // yeah, there are a lot of qTypes\nfunc Lookup(ctx context.Context, qType uint16, qName, noSearchDomain string) (RRs, int, error) {\n\tvar answer RRs\n\tr := &net.Resolver{StrictErrors: true}\n\tswitch qType {\n\tcase dns.TypeA:\n\t\tips, err := lookupIP(ctx, \"ip4\", qName, noSearchDomain, r)\n\t\tif err != nil {\n\t\t\treturn makeError(err)\n\t\t}\n\t\tfor _, ip := range ips {\n\t\t\tif ip4 := ip.To4(); ip4 != nil {\n\t\t\t\tanswer = append(answer, &dns.A{\n\t\t\t\t\tHdr: NewHeader(qName, qType),\n\t\t\t\t\tA:   ip4,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\tcase dns.TypeAAAA:\n\t\tips, err := lookupIP(ctx, \"ip6\", qName, noSearchDomain, r)\n\t\tif err != nil {\n\t\t\treturn makeError(err)\n\t\t}\n\t\tfor _, ip := range ips {\n\t\t\tif ip16 := ip.To16(); ip16 != nil {\n\t\t\t\tanswer = append(answer, &dns.AAAA{\n\t\t\t\t\tHdr:  NewHeader(qName, qType),\n\t\t\t\t\tAAAA: ip16,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\tcase dns.TypePTR:\n\t\tvar names []string\n\t\tip, err := PtrAddress(qName)\n\t\tif err != nil {\n\t\t\treturn makeError(err)\n\t\t}\n\t\tif names, err = r.LookupAddr(ctx, ip.String()); err != nil {\n\t\t\treturn makeError(err)\n\t\t}\n\t\tanswer = make(RRs, len(names))\n\t\tfor i, n := range names {\n\t\t\tanswer[i] = &dns.PTR{\n\t\t\t\tHdr: NewHeader(qName, qType),\n\t\t\t\tPtr: n,\n\t\t\t}\n\t\t}\n\tcase dns.TypeCNAME:\n\t\tname, final := useLookupName(qName, noSearchDomain)\n\t\ttarget, err := r.LookupCNAME(ctx, name)\n\t\tif err != nil && !final {\n\t\t\ttarget, err = r.LookupCNAME(ctx, qName)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn makeError(err)\n\t\t}\n\t\tanswer = RRs{&dns.CNAME{\n\t\t\tHdr:    NewHeader(qName, qType),\n\t\t\tTarget: target,\n\t\t}}\n\tcase dns.TypeMX:\n\t\tmx, err := r.LookupMX(ctx, qName[:len(qName)-1])\n\t\tif err != nil {\n\t\t\treturn makeError(err)\n\t\t}\n\t\tanswer = make(RRs, len(mx))\n\t\tfor i, r := range mx {\n\t\t\tanswer[i] = &dns.MX{\n\t\t\t\tHdr:        NewHeader(qName, qType),\n\t\t\t\tPreference: r.Pref,\n\t\t\t\tMx:         r.Host,\n\t\t\t}\n\t\t}\n\tcase dns.TypeNS:\n\t\tns, err := r.LookupNS(ctx, qName[:len(qName)-1])\n\t\tif err != nil {\n\t\t\treturn makeError(err)\n\t\t}\n\t\tanswer = make(RRs, len(ns))\n\t\tfor i, n := range ns {\n\t\t\tanswer[i] = &dns.NS{\n\t\t\t\tHdr: NewHeader(qName, qType),\n\t\t\t\tNs:  n.Host,\n\t\t\t}\n\t\t}\n\tcase dns.TypeSRV:\n\t\t_, srvs, err := r.LookupSRV(ctx, \"\", \"\", qName[:len(qName)-1])\n\t\tif err != nil {\n\t\t\trrs, rCode, err := makeError(err)\n\t\t\tif rCode != dns.RcodeNameError {\n\t\t\t\treturn rrs, rCode, err\n\t\t\t}\n\t\t\t// The LookupSRV doesn't use libc for the lookup even when told to do so, amd normal\n\t\t\t// search-path expansion doesn't seem to apply. Let's see if the FQN is different, and\n\t\t\t// if so, try that instead.\n\t\t\tfqn := svcFQN(ctx, qName, r)\n\t\t\tif fqn == \"\" || fqn == qName {\n\t\t\t\treturn rrs, rCode, err\n\t\t\t}\n\t\t\tvar fqnErr error\n\t\t\tif _, srvs, fqnErr = r.LookupSRV(ctx, \"\", \"\", fqn); fqnErr != nil {\n\t\t\t\t// Return original error\n\t\t\t\treturn rrs, rCode, err\n\t\t\t}\n\t\t}\n\t\tanswer = make(RRs, len(srvs))\n\t\tfor i, s := range srvs {\n\t\t\tanswer[i] = &dns.SRV{\n\t\t\t\tHdr:      NewHeader(qName, qType),\n\t\t\t\tTarget:   s.Target,\n\t\t\t\tPort:     s.Port,\n\t\t\t\tPriority: s.Priority,\n\t\t\t\tWeight:   s.Weight,\n\t\t\t}\n\t\t}\n\tcase dns.TypeTXT:\n\t\tnames, err := r.LookupTXT(ctx, qName)\n\t\tif err != nil {\n\t\t\treturn makeError(err)\n\t\t}\n\t\tanswer = RRs{&dns.TXT{\n\t\t\tHdr: NewHeader(qName, qType),\n\t\t\tTxt: names,\n\t\t}}\n\tdefault:\n\t\treturn nil, dns.RcodeNotImplemented, status.Errorf(codes.Unimplemented, \"unsupported DNS query type %s\", dns.TypeToString[qType])\n\t}\n\treturn answer, dns.RcodeSuccess, nil\n}\n\nfunc svcFQN(ctx context.Context, name string, r *net.Resolver) string {\n\tparts := strings.Split(name, \".\")\n\tif !(len(parts) > 2 && strings.HasPrefix(parts[0], \"_\") && strings.HasPrefix(parts[1], \"_\")) {\n\t\treturn \"\"\n\t}\n\tsvcName := strings.Join(parts[2:], \".\")\n\tips, err := r.LookupIP(ctx, \"ip\", svcName[:len(svcName)-1])\n\tif err != nil || len(ips) < 1 {\n\t\treturn \"\"\n\t}\n\tnames, err := r.LookupAddr(ctx, ips[0].String())\n\tif err != nil || len(names) < 1 {\n\t\treturn \"\"\n\t}\n\tfqn := names[0]\n\tix := strings.Index(fqn, svcName)\n\tif ix < 0 {\n\t\treturn \"\"\n\t}\n\tfqn = parts[0] + \".\" + parts[1] + \".\" + fqn[ix:]\n\treturn fqn\n}\n"
  },
  {
    "path": "pkg/dnsproxy/lookup_test.go",
    "content": "package dnsproxy\n\nimport (\n\t\"net/netip\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/miekg/dns\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog/testutil\"\n)\n\nfunc TestLookup(t *testing.T) {\n\ttype tType struct {\n\t\tqType uint16\n\t\tqName string\n\t}\n\ttests := []tType{\n\t\t{\n\t\t\tdns.TypeA,\n\t\t\t\"google.com.\",\n\t\t},\n\t\t{\n\t\t\tdns.TypeCNAME,\n\t\t\t\"_smpp_._tcp.golang.org.\",\n\t\t},\n\t\t{\n\t\t\tdns.TypePTR,\n\t\t\t\"78.217.250.142.in-addr.arpa.\",\n\t\t},\n\t\t{\n\t\t\tdns.TypeMX,\n\t\t\t\"gmail.com.\",\n\t\t},\n\t\t{\n\t\t\tdns.TypeTXT,\n\t\t\t\"dns.google.\",\n\t\t},\n\t\t{\n\t\t\tdns.TypeSRV,\n\t\t\t\"_myservice._tcp.tada.se.\",\n\t\t},\n\t}\n\t// AAAA returns an error on Windows:\n\t// \"getaddrinfow: The requested name is valid, but no data of the requested type was found\"\n\tif runtime.GOOS != \"windows\" {\n\t\ttests = append(tests, tType{\n\t\t\tdns.TypeAAAA,\n\t\t\t\"google.com.\",\n\t\t})\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(dns.TypeToString[tt.qType], func(t *testing.T) {\n\t\t\tif tt.qType == dns.TypeSRV && runtime.GOOS == \"darwin\" {\n\t\t\t\tt.Skip(\"SRV sporadically fails to parse reply on darwin\")\n\t\t\t}\n\t\t\tctx := testutil.NewContext(t, false)\n\t\t\tgot, _, err := Lookup(ctx, tt.qType, tt.qName, \"\")\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Greater(t, len(got), 0)\n\t\t})\n\t}\n}\n\nfunc TestPtrAddress_v4(t *testing.T) {\n\tip, err := PtrAddress(\"32.127.168.192.in-addr.arpa.\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, netip.AddrFrom4([4]byte{192, 168, 127, 32}), ip)\n}\n\nfunc TestPtrAddress_v6(t *testing.T) {\n\tip, err := PtrAddress(\"b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, netip.MustParseAddr(\"2001:db8::567:89ab\"), ip)\n}\n"
  },
  {
    "path": "pkg/dnsproxy/resolvefile_unix.go",
    "content": "//go:build !windows\n\npackage dnsproxy\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\ntype ResolveFile struct {\n\tPort        int\n\tDomain      string\n\tNameservers []string\n\tSearch      []string\n\tSortList    []string\n\tOptions     []string\n}\n\nfunc ReadResolveFile(fileName string) (*ResolveFile, error) {\n\tfl, err := os.Open(fileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer fl.Close()\n\treturn ParseResolveFile(fileName, fl)\n}\n\nfunc ParseResolveFile(fileName string, rdr io.Reader) (*ResolveFile, error) {\n\tsc := bufio.NewScanner(rdr)\n\trf := ResolveFile{}\n\tline := 0\n\n\tonlyOne := func(key string) error {\n\t\treturn fmt.Errorf(\"%q must have a value at %s line %d\", key, fileName, line)\n\t}\n\n\tfor sc.Scan() {\n\t\tline++\n\t\ttxt := strings.TrimSpace(sc.Text())\n\t\tif len(txt) == 0 || txt[0] == '#' || txt[0] == ';' {\n\t\t\tcontinue\n\t\t}\n\t\tfields := strings.Fields(txt)\n\t\tfc := len(fields)\n\t\tif fc == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tkey := fields[0]\n\t\tif fc == 1 {\n\t\t\treturn nil, fmt.Errorf(\"%q must have a value at %s line %d\", key, fileName, line)\n\t\t}\n\t\tvalue := fields[1]\n\t\tswitch key {\n\t\tcase \"port\":\n\t\t\tif fc != 2 {\n\t\t\t\treturn nil, onlyOne(key)\n\t\t\t}\n\t\t\tvar err error\n\t\t\trf.Port, err = strconv.Atoi(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"%q is not a valid integer at %s line %d\", key, fileName, line)\n\t\t\t}\n\t\tcase \"domain\":\n\t\t\tif fc != 2 {\n\t\t\t\treturn nil, onlyOne(key)\n\t\t\t}\n\t\t\trf.Domain = value\n\t\tcase \"nameserver\":\n\t\t\tif fc != 2 {\n\t\t\t\treturn nil, onlyOne(key)\n\t\t\t}\n\t\t\trf.Nameservers = append(rf.Nameservers, value)\n\t\tcase \"search\":\n\t\t\trf.Search = fields[1:]\n\t\tcase \"sortlist\":\n\t\t\trf.SortList = fields[1:]\n\t\tcase \"options\":\n\t\t\trf.Options = fields[1:]\n\t\tdefault:\n\t\t\t// This reader doesn't do options just yet\n\t\t\treturn nil, fmt.Errorf(\"%q is not a recognized key at %s line %d\", key, fileName, line)\n\t\t}\n\t}\n\treturn &rf, nil\n}\n\nfunc (r *ResolveFile) String() string {\n\tvar buf strings.Builder\n\t_, _ = r.WriteTo(&buf)\n\treturn buf.String()\n}\n\nfunc (r *ResolveFile) Equals(o *ResolveFile) bool {\n\tif r == nil || o == nil {\n\t\treturn r == o\n\t}\n\treturn r.Port == o.Port &&\n\t\tr.Domain == o.Domain &&\n\t\tslices.Equal(r.Nameservers, o.Nameservers) &&\n\t\tslices.Equal(r.Search, o.Search) &&\n\t\tslices.Equal(r.SortList, o.SortList) &&\n\t\tslices.Equal(r.Options, o.Options)\n}\n\nfunc (r *ResolveFile) Write(fileName string) error {\n\tvar buf bytes.Buffer\n\t_, _ = r.WriteTo(&buf)\n\treturn os.WriteFile(fileName, buf.Bytes(), 0o644)\n}\n\nfunc (r *ResolveFile) WriteTo(buf io.Writer) (l int64, err error) {\n\tn := ioutil.Print(buf, \"# Generated by telepresence\\n\\n\")\n\tif r.Port > 0 {\n\t\tn += ioutil.Printf(buf, \"port %d\\n\", r.Port)\n\t}\n\tif r.Domain != \"\" {\n\t\tn += ioutil.Printf(buf, \"domain %s\\n\", r.Domain)\n\t}\n\tfor _, ns := range r.Nameservers {\n\t\tn += ioutil.Printf(buf, \"nameserver %s\\n\", ns)\n\t}\n\tif len(r.Search) > 0 {\n\t\tn += ioutil.Printf(buf, \"search %s\\n\", strings.Join(r.Search, \" \"))\n\t}\n\tif len(r.Options) > 0 {\n\t\tn += ioutil.Printf(buf, \"options %s\\n\", strings.Join(r.Options, \" \"))\n\t}\n\tif len(r.SortList) > 0 {\n\t\tn += ioutil.Printf(buf, \"sortlist %s\\n\", strings.Join(r.SortList, \" \"))\n\t}\n\treturn int64(n), nil\n}\n"
  },
  {
    "path": "pkg/dnsproxy/resolvefile_unix_test.go",
    "content": "//go:build !windows\n\npackage dnsproxy\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestReadResolveFile(t *testing.T) {\n\tconst fileContent = `\n# Some comment starting with hash\n; Some comment starting with semicolon\nport 33764\ndomain example.com\nnameserver 1.1.1.1\nnameserver  8.8.8.8\nsearch example.com svc.example.com ns.svc.example.com\nsortlist 130.155.160.0/255.255.240.0 130.155.0.0\noptions timeout:10 single-request-reopen\n; Another comment\n# And yet another comment\n`\n\trdr := strings.NewReader(fileContent)\n\trf, err := ParseResolveFile(\"test.conf\", rdr)\n\trequire.NoError(t, err)\n\trequire.Equal(t, rf.Port, 33764)\n\trequire.Equal(t, rf.Domain, \"example.com\")\n\trequire.Equal(t, rf.Nameservers, []string{\"1.1.1.1\", \"8.8.8.8\"})\n\trequire.Equal(t, rf.Search, []string{\"example.com\", \"svc.example.com\", \"ns.svc.example.com\"})\n\trequire.Equal(t, rf.SortList, []string{\"130.155.160.0/255.255.240.0\", \"130.155.0.0\"})\n\trequire.Equal(t, rf.Options, []string{\"timeout:10\", \"single-request-reopen\"})\n}\n"
  },
  {
    "path": "pkg/dnsproxy/rpc.go",
    "content": "package dnsproxy\n\nimport (\n\t\"github.com/miekg/dns\"\n\t\"google.golang.org/grpc/codes\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\tgrpcErrors \"github.com/telepresenceio/telepresence/v2/pkg/grpc/errors\"\n)\n\nfunc ToRPC(rrs RRs, rCode int) (*manager.DNSResponse, error) {\n\tl := 0\n\tfor _, rr := range rrs {\n\t\tl += dns.Len(rr)\n\t}\n\trsp := &manager.DNSResponse{RCode: int32(rCode)}\n\trrb := make([]byte, l)\n\toff := 0\n\tfor _, rr := range rrs {\n\t\tvar err error\n\t\tif off, err = dns.PackRR(rr, rrb, off, nil, false); err != nil {\n\t\t\treturn nil, grpcErrors.Errorf(codes.Internal, \"unable to pack DNS reply: %v\", err)\n\t\t}\n\t}\n\trsp.Rrs = rrb\n\treturn rsp, nil\n}\n\nfunc FromRPC(r *manager.DNSResponse) (RRs, int, error) {\n\trrb := r.Rrs\n\tvar rrs RRs\n\toff := 0\n\tfor len(rrb) > off {\n\t\tvar rr dns.RR\n\t\tvar err error\n\t\tif rr, off, err = dns.UnpackRR(rrb, off); err != nil {\n\t\t\treturn nil, dns.RcodeFormatError, grpcErrors.Errorf(codes.InvalidArgument, \"unable to unpack DNS response: %v\", err)\n\t\t}\n\t\trrs = append(rrs, rr)\n\t}\n\treturn rrs, int(r.RCode), nil\n}\n"
  },
  {
    "path": "pkg/dos/aferofs/wrapper.go",
    "content": "package aferofs\n\nimport (\n\t\"errors\"\n\t\"io/fs\"\n\t\"os\"\n\n\t\"github.com/spf13/afero\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n)\n\ntype wrapFs struct {\n\tafero.Fs\n}\n\nfunc Wrap(fs afero.Fs) dos.FileSystem {\n\treturn wrapFs{fs}\n}\n\nfunc wrapFile(f afero.File, err error) (dos.File, error) {\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn file{f}, err\n}\n\nfunc (a wrapFs) Abs(name string) (string, error) {\n\treturn \"\", errors.New(\"afero.Fs does not implement Abs\")\n}\n\nfunc (a wrapFs) Chdir(path string) error {\n\treturn errors.New(\"afero.Fs does not implement Chdir\")\n}\n\nfunc (a wrapFs) Create(name string) (dos.File, error) {\n\treturn wrapFile(a.Fs.Create(name))\n}\n\nfunc (a wrapFs) Getwd() (string, error) {\n\treturn \"\", errors.New(\"afero.Fs does not implement Getwd\")\n}\n\nfunc (a wrapFs) ReadDir(name string) ([]fs.DirEntry, error) {\n\tdir, err := a.Open(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer dir.Close()\n\treturn dir.ReadDir(0)\n}\n\nfunc (a wrapFs) ReadFile(name string) ([]byte, error) {\n\treturn afero.ReadFile(a.Fs, name)\n}\n\nfunc (a wrapFs) RealPath(name string) (string, error) {\n\tif rp, ok := a.Fs.(interface{ RealPath(string) (string, error) }); ok {\n\t\treturn rp.RealPath(name)\n\t}\n\treturn \"\", errors.New(\"RealPath is not implemented\")\n}\n\nfunc (a wrapFs) Open(name string) (dos.File, error) {\n\treturn wrapFile(a.Fs.Open(name))\n}\n\nfunc (a wrapFs) OpenFile(name string, flag int, perm fs.FileMode) (dos.File, error) {\n\treturn wrapFile(a.Fs.OpenFile(name, flag, perm))\n}\n\nfunc (a wrapFs) Symlink(oldName, newName string) error {\n\tif lfs, ok := a.Fs.(afero.Linker); ok {\n\t\treturn lfs.SymlinkIfPossible(oldName, newName)\n\t}\n\treturn &os.LinkError{Op: \"symlink\", Old: oldName, New: newName, Err: afero.ErrNoSymlink}\n}\n\nfunc (a wrapFs) WriteFile(name string, data []byte, perm fs.FileMode) error {\n\treturn afero.WriteFile(a.Fs, name, data, perm)\n}\n\n// The afero.File lacks ReadDir. Instances implement fs.ReadDirFile though.\ntype file struct {\n\tafero.File\n}\n\n// dirEntry provides adapter from os.FileInfo to fs.DirEntry.\ntype dirEntry struct {\n\tfs.FileInfo\n}\n\nfunc (d dirEntry) Type() fs.FileMode { return d.FileInfo.Mode().Type() }\n\nfunc (d dirEntry) Info() (fs.FileInfo, error) { return d.FileInfo, nil }\n\nfunc (a file) ReadDir(count int) ([]fs.DirEntry, error) {\n\tif d, ok := a.File.(fs.ReadDirFile); ok {\n\t\treturn d.ReadDir(count)\n\t}\n\tfis, err := a.Readdir(count) //nolint:forbidigo // this is not an os.File\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdes := make([]fs.DirEntry, len(fis))\n\tfor i, fi := range fis {\n\t\tdes[i] = dirEntry{fi}\n\t}\n\treturn des, nil\n}\n"
  },
  {
    "path": "pkg/dos/env.go",
    "content": "package dos\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n)\n\n// Env is an abstraction of the environment related functions with the same name in the os package.\ntype Env interface {\n\tEnviron() []string\n\tExpandEnv(string) string\n\tGetenv(string) string\n\tSetenv(string, string) error\n\tLookup(string) (string, bool)\n}\n\ntype MapEnv map[string]string\n\n// FromEnvPairs converts a slice of strings int the form KEY=VALUE into\n// a map[string]string.\nfunc FromEnvPairs(env []string) MapEnv {\n\tm := make(MapEnv, len(env))\n\tm.MergeEnvPairs(env)\n\treturn m\n}\n\n// MergeEnvPairs merges env entries in the form of KEY=VALUE into the\n// given map, giving the env entries higher priority.\nfunc (e MapEnv) MergeEnvPairs(env []string) {\n\tfor _, ep := range env {\n\t\tif ix := strings.IndexByte(ep, '='); ix > 0 {\n\t\t\te[ep[:ix]] = ep[ix+1:]\n\t\t}\n\t}\n}\n\nfunc (e MapEnv) Environ() []string {\n\tks := make([]string, len(e))\n\ti := 0\n\tfor k := range e {\n\t\tks[i] = k\n\t\ti++\n\t}\n\tsort.Strings(ks)\n\tfor i, k := range ks {\n\t\tks[i] = k + \"=\" + e[k]\n\t}\n\treturn ks\n}\n\nfunc (e MapEnv) ExpandEnv(s string) string {\n\treturn os.Expand(s, e.Getenv)\n}\n\nfunc (e MapEnv) Getenv(key string) string {\n\treturn e[key]\n}\n\nfunc (e MapEnv) Setenv(key, value string) error {\n\te[key] = value\n\treturn nil\n}\n\nfunc (e MapEnv) Lookup(key string) (string, bool) {\n\ts, ok := e[key]\n\treturn s, ok\n}\n\ntype envKey struct{}\n\nfunc WithEnv(ctx context.Context, env Env) context.Context {\n\treturn context.WithValue(ctx, envKey{}, env)\n}\n\n// EnvAPI returns the Env that has been registered with the given context, or\n// the instance that delegates to the env functions in the os package.\nfunc EnvAPI(ctx context.Context) Env {\n\tif e, ok := ctx.Value(envKey{}).(Env); ok {\n\t\treturn e\n\t}\n\treturn osEnv{}\n}\n\nfunc Environ(ctx context.Context) []string {\n\treturn EnvAPI(ctx).Environ()\n}\n\nfunc ExpandEnv(ctx context.Context, s string) string {\n\treturn EnvAPI(ctx).ExpandEnv(s)\n}\n\nfunc Getenv(ctx context.Context, key string) string {\n\treturn EnvAPI(ctx).Getenv(key)\n}\n\nfunc Setenv(ctx context.Context, key, value string) error {\n\treturn EnvAPI(ctx).Setenv(key, value)\n}\n\nfunc LookupEnv(ctx context.Context, key string) (string, bool) {\n\treturn EnvAPI(ctx).Lookup(key)\n}\n\ntype osEnv struct{}\n\nfunc (osEnv) Environ() []string {\n\treturn os.Environ()\n}\n\nfunc (osEnv) ExpandEnv(s string) string {\n\treturn os.ExpandEnv(s)\n}\n\nfunc (osEnv) Getenv(s string) string {\n\treturn os.Getenv(s)\n}\n\nfunc (osEnv) Setenv(key, value string) error {\n\treturn os.Setenv(key, value)\n}\n\nfunc (osEnv) Lookup(s string) (string, bool) {\n\treturn os.LookupEnv(s)\n}\n"
  },
  {
    "path": "pkg/dos/exe.go",
    "content": "package dos\n\nimport (\n\t\"context\"\n\t\"os\"\n)\n\n// Exe is an abstraction of the executable related functions with the same name in the os package.\ntype Exe interface {\n\tExecutable() (string, error)\n}\n\ntype exeKey struct{}\n\nfunc WithExe(ctx context.Context, exe Exe) context.Context {\n\treturn context.WithValue(ctx, exeKey{}, exe)\n}\n\n// ExeAPI returns the Exe that has been registered with the given context, or\n// the instance that delegates to the corresponding functions in the os package.\nfunc ExeAPI(ctx context.Context) Exe {\n\tif e, ok := ctx.Value(exeKey{}).(Exe); ok {\n\t\treturn e\n\t}\n\treturn osExe{}\n}\n\nfunc Executable(ctx context.Context) (string, error) {\n\treturn ExeAPI(ctx).Executable()\n}\n\ntype osExe struct{}\n\nfunc (osExe) Executable() (string, error) {\n\treturn os.Executable()\n}\n"
  },
  {
    "path": "pkg/dos/filesystem.go",
    "content": "package dos\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"syscall\" //nolint:depguard // \"unix\" don't work on windows\n)\n\n// File represents a file in the filesystem. The os.File struct implements this interface.\ntype File interface {\n\tio.Closer\n\tio.Reader\n\tio.ReaderAt\n\tio.Seeker\n\tio.Writer\n\tio.WriterAt\n\n\tName() string\n\tReaddir(count int) ([]fs.FileInfo, error)\n\tReaddirnames(n int) ([]string, error)\n\tStat() (fs.FileInfo, error)\n\tSync() error\n\tTruncate(size int64) error\n\tWriteString(s string) (ret int, err error)\n\tReadDir(count int) ([]fs.DirEntry, error)\n}\n\ntype OwnedFile interface {\n\tChown(uid, gid int) error\n}\n\n// FileSystem is an interface that implements functions in the os package.\ntype FileSystem interface {\n\tAbs(name string) (string, error)\n\tChdir(name string) error\n\tCreate(name string) (File, error)\n\tGetwd() (string, error)\n\tMkdir(name string, perm fs.FileMode) error\n\tMkdirAll(name string, perm fs.FileMode) error\n\tOpen(name string) (File, error)\n\tOpenFile(name string, flag int, perm fs.FileMode) (File, error)\n\tReadDir(name string) ([]fs.DirEntry, error)\n\tReadFile(name string) ([]byte, error)\n\tRealPath(name string) (string, error)\n\tRemove(name string) error\n\tRemoveAll(name string) error\n\tRename(oldName, newName string) error\n\tStat(name string) (fs.FileInfo, error)\n\tSymlink(oldName, newName string) error\n\tWriteFile(name string, data []byte, perm fs.FileMode) error\n}\n\ntype osFs struct {\n\ttpUID int\n\ttpGID int\n}\n\nfunc (*osFs) Abs(name string) (string, error) {\n\treturn filepath.Abs(name)\n}\n\nfunc (*osFs) Chdir(name string) error {\n\treturn os.Chdir(name)\n}\n\nfunc (fs *osFs) Create(name string) (File, error) {\n\tf, err := os.Create(name)\n\tif err != nil {\n\t\t// It's important to return a File nil here, not a File that represents an *os.File nil.\n\t\treturn nil, err\n\t}\n\treturn fs.chownFile(f)\n}\n\nfunc (*osFs) Getwd() (string, error) {\n\treturn os.Getwd()\n}\n\nfunc (fs *osFs) Mkdir(name string, perm fs.FileMode) error {\n\treturn fs.chown(os.Mkdir(name, perm), name)\n}\n\n// MkdirAll is a slightly modified version the same function in of Go 1.19.3's os/path.go.\nfunc (fs *osFs) MkdirAll(path string, perm fs.FileMode) error {\n\t// Fast path: if we can tell whether path is a directory or file, stop with success or error.\n\tdir, err := os.Stat(path)\n\tif err == nil {\n\t\tif dir.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\treturn &os.PathError{Op: \"mkdir\", Path: path, Err: syscall.ENOTDIR} //nolint:forbidigo // we want the same error\n\t}\n\n\t// Slow path: make sure parent exists and then call Mkdir for path.\n\ti := len(path)\n\tfor i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator.\n\t\ti--\n\t}\n\n\tj := i\n\tfor j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element.\n\t\tj--\n\t}\n\n\tif j > 1 {\n\t\t// Create parent.\n\t\tif err = fs.MkdirAll(path[:j-1], perm); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\t// Parent now exists; invoke Mkdir and use its result.\n\terr = fs.Mkdir(path, perm)\n\tif err != nil {\n\t\t// Handle arguments like \"foo/.\" by\n\t\t// double-checking that directory doesn't exist.\n\t\tdir, err1 := os.Lstat(path)\n\t\tif err1 == nil && dir.IsDir() {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (*osFs) Open(name string) (File, error) {\n\tf, err := os.Open(name)\n\tif err != nil {\n\t\t// It's important to return a File nil here, not a File that represents an *os.File nil.\n\t\treturn nil, err\n\t}\n\treturn f, nil\n}\n\nfunc (fs *osFs) OpenFile(name string, flag int, perm fs.FileMode) (File, error) {\n\tif fs.mustChown() {\n\t\tif (flag & os.O_CREATE) == os.O_CREATE {\n\t\t\tif _, err := os.Stat(name); errors.Is(err, os.ErrNotExist) {\n\t\t\t\tf, err := os.OpenFile(name, flag, perm)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn fs.chownFile(f)\n\t\t\t}\n\t\t}\n\t}\n\tf, err := os.OpenFile(name, flag, perm)\n\tif err != nil {\n\t\t// It's important to return a File nil here, not a File that represents an *os.File nil.\n\t\treturn nil, err\n\t}\n\treturn f, nil\n}\n\nfunc (*osFs) ReadDir(name string) ([]fs.DirEntry, error) {\n\treturn os.ReadDir(name)\n}\n\nfunc (*osFs) ReadFile(name string) ([]byte, error) {\n\treturn os.ReadFile(name)\n}\n\nfunc (*osFs) RealPath(name string) (string, error) {\n\treturn filepath.Abs(name)\n}\n\nfunc (*osFs) Remove(name string) error {\n\treturn os.Remove(name)\n}\n\nfunc (*osFs) RemoveAll(name string) error {\n\treturn os.RemoveAll(name)\n}\n\nfunc (*osFs) Rename(oldName, newName string) error {\n\treturn os.Rename(oldName, newName)\n}\n\nfunc (*osFs) Stat(name string) (fs.FileInfo, error) {\n\treturn os.Stat(name)\n}\n\nfunc (*osFs) Symlink(oldName, newName string) error {\n\treturn os.Symlink(oldName, newName)\n}\n\nfunc (fs *osFs) WriteFile(name string, data []byte, perm fs.FileMode) error {\n\tif fs.mustChown() {\n\t\tif _, err := os.Stat(name); errors.Is(err, os.ErrNotExist) {\n\t\t\treturn fs.chown(os.WriteFile(name, data, perm), name)\n\t\t}\n\t}\n\treturn os.WriteFile(name, data, perm)\n}\n\nfunc (fs *osFs) mustChown() bool {\n\treturn fs.tpUID > 0 || fs.tpGID > 0\n}\n\nfunc (fs *osFs) chown(err error, name string) error {\n\tif err == nil && fs.mustChown() {\n\t\terr = os.Chown(name, fs.tpUID, fs.tpGID)\n\t}\n\treturn err\n}\n\nfunc (fs *osFs) chownFile(f File) (File, error) {\n\tif fs.mustChown() {\n\t\tvar err error\n\t\tif of, ok := f.(OwnedFile); ok {\n\t\t\terr = of.Chown(fs.tpUID, fs.tpGID)\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"chown is not supported by %T\", f)\n\t\t}\n\t\tif err != nil {\n\t\t\t_ = f.Close()\n\t\t\t_ = fs.Remove(f.Name())\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn f, nil\n}\n\ntype fsKey struct{}\n\n// WithFS assigns the FileSystem to be used by subsequent file system related dos functions.\nfunc WithFS(ctx context.Context, fs FileSystem) context.Context {\n\treturn context.WithValue(ctx, fsKey{}, fs)\n}\n\nfunc getFS(ctx context.Context) FileSystem {\n\tif f, ok := ctx.Value(fsKey{}).(FileSystem); ok {\n\t\treturn f\n\t}\n\tof := newOS(ctx)\n\treturn &of\n}\n\nfunc newOS(ctx context.Context) osFs {\n\tof := osFs{}\n\tif env, ok := LookupEnv(ctx, \"TELEPRESENCE_UID\"); ok {\n\t\tof.tpUID, _ = strconv.Atoi(env)\n\t}\n\tif env, ok := LookupEnv(ctx, \"TELEPRESENCE_GID\"); ok {\n\t\tof.tpGID, _ = strconv.Atoi(env)\n\t}\n\treturn of\n}\n\n// Abs is like filepath.Abs but delegates to the context's FS.\nfunc Abs(ctx context.Context, name string) (string, error) {\n\treturn getFS(ctx).Abs(name)\n}\n\n// Chdir is like os.Chdir but delegates to the context's FS.\nfunc Chdir(ctx context.Context, path string) error {\n\treturn getFS(ctx).Chdir(path)\n}\n\n// Create is like os.Create but delegates to the context's FS.\nfunc Create(ctx context.Context, name string) (File, error) {\n\treturn getFS(ctx).Create(name)\n}\n\n// Getwd is like os.Getwd but delegates to the context's FS.\nfunc Getwd(ctx context.Context) (string, error) {\n\treturn getFS(ctx).Getwd()\n}\n\n// Mkdir is like os.Mkdir but delegates to the context's FS.\nfunc Mkdir(ctx context.Context, name string, perm fs.FileMode) error {\n\treturn getFS(ctx).Mkdir(name, perm)\n}\n\n// MkdirAll is like os.MkdirAll but delegates to the context's FS.\nfunc MkdirAll(ctx context.Context, name string, perm fs.FileMode) error {\n\treturn getFS(ctx).MkdirAll(name, perm)\n}\n\n// Open is like os.Open but delegates to the context's FS.\nfunc Open(ctx context.Context, name string) (File, error) {\n\treturn getFS(ctx).Open(name)\n}\n\n// OpenFile is like os.OpenFile but delegates to the context's FS.\nfunc OpenFile(ctx context.Context, name string, flag int, perm fs.FileMode) (File, error) {\n\treturn getFS(ctx).OpenFile(name, flag, perm)\n}\n\n// ReadDir is like os.ReadDir but delegates to the context's FS.\nfunc ReadDir(ctx context.Context, name string) ([]fs.DirEntry, error) {\n\treturn getFS(ctx).ReadDir(name)\n}\n\n// ReadFile is like os.ReadFile but delegates to the context's FS.\nfunc ReadFile(ctx context.Context, name string) ([]byte, error) { // MODIFIED\n\treturn getFS(ctx).ReadFile(name)\n}\n\n// RealPath returns the real path in the underlying os filesystem or\n// an error if there's no os filesystem.\nfunc RealPath(ctx context.Context, name string) (string, error) {\n\treturn getFS(ctx).RealPath(name)\n}\n\n// Remove is like os.Remove but delegates to the context's FS.\nfunc Remove(ctx context.Context, name string) error {\n\treturn getFS(ctx).Remove(name)\n}\n\n// RemoveAll is like os.RemoveAll but delegates to the context's FS.\nfunc RemoveAll(ctx context.Context, name string) error {\n\treturn getFS(ctx).RemoveAll(name)\n}\n\n// Rename is like os.Rename but delegates to the context's FS.\nfunc Rename(ctx context.Context, oldName, newName string) error {\n\treturn getFS(ctx).Rename(oldName, newName)\n}\n\nfunc WriteFile(ctx context.Context, name string, data []byte, perm fs.FileMode) error {\n\treturn getFS(ctx).WriteFile(name, data, perm)\n}\n\n// Stat is like os.Stat but delegates to the context's FS.\nfunc Stat(ctx context.Context, name string) (fs.FileInfo, error) {\n\treturn getFS(ctx).Stat(name)\n}\n\n// Symlink is like os.Symlink but delegates to the context's FS.\nfunc Symlink(ctx context.Context, oldName, newName string) error {\n\treturn getFS(ctx).Symlink(oldName, newName)\n}\n"
  },
  {
    "path": "pkg/dos/filesystem_test.go",
    "content": "package dos_test\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"testing\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/spf13/afero\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos/aferofs\"\n)\n\nfunc TestWithFS(t *testing.T) {\n\tappFS := afero.NewMemMapFs()\n\tcData := []byte(\"file c\\n\")\n\tdData := []byte(\"file d\\n\")\n\trequire.NoError(t, appFS.MkdirAll(\"a/b\", 0o755))\n\trequire.NoError(t, afero.WriteFile(appFS, \"/a/b/c.txt\", cData, 0o644))\n\trequire.NoError(t, afero.WriteFile(appFS, \"/a/d.txt\", dData, 0o644))\n\n\tctx := dos.WithFS(testutil.NewContext(t, false), dos.WorkingDirWrapper(aferofs.Wrap(appFS)))\n\n\trequire.NoError(t, dos.Chdir(ctx, \"/a/b\"))\n\tdata, err := dos.ReadFile(ctx, \"c.txt\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, cData, data)\n\n\trequire.NoError(t, dos.Chdir(ctx, \"../..\"))\n\tf, err := dos.Open(ctx, \"/a/d.txt\")\n\trequire.NoError(t, err)\n\tdata, err = io.ReadAll(f)\n\trequire.NoError(t, err)\n\trequire.NoError(t, f.Close())\n\trequire.Equal(t, dData, data)\n}\n\nfunc TestFileNil(t *testing.T) {\n\t// This function will return a File interface that points to a nil *os.File. That's\n\t// not the same as a File interface which is nil.\n\tneverDoThis := func(name string) (dos.File, error) {\n\t\treturn os.Open(name)\n\t}\n\n\tid, err := uuid.NewUUID()\n\tbadFile := filepath.Join(fmt.Sprintf(\"%c%s\", filepath.Separator, id), \"does\", \"not\", \"exist\")\n\trequire.NoError(t, err)\n\tf, err := neverDoThis(badFile)\n\tassert.Error(t, err)\n\tassert.True(t, f != nil) // Do NOT change this to assert.NotNil(t, f) because that test is flawed.\n\tassert.Nil(t, f)         // Told you so. It is flawed!\n\n\tf, err = dos.Open(context.Background(), badFile)\n\tassert.Error(t, err)\n\tassert.True(t, f == nil) // Do NOT change this to assert.Nil(t, f) because that test is flawed.\n\tf, err = dos.OpenFile(context.Background(), badFile, os.O_RDONLY, 0o600)\n\tassert.Error(t, err)\n\tassert.True(t, f == nil) // Do NOT change this to assert.Nil(t, f) because that test is flawed.\n\tf, err = dos.Create(context.Background(), badFile)\n\tassert.Error(t, err)\n\tassert.True(t, f == nil) // Do NOT change this to assert.Nil(t, f) because that test is flawed.\n}\n\n// Example using afero.MemMapFs.\nfunc ExampleWithFS() {\n\tappFS := afero.NewCopyOnWriteFs(afero.NewOsFs(), afero.NewMemMapFs())\n\tctx := dos.WithFS(context.Background(), aferofs.Wrap(appFS))\n\n\tif err := dos.MkdirAll(ctx, \"/etc\", 0o700); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\thosts, err := dos.Create(ctx, \"/etc/example.conf\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Fprintln(hosts, \"example = conf\")\n\thosts.Close()\n\n\tif hosts, err = dos.Open(ctx, \"/etc/example.conf\"); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\t_, err = io.Copy(os.Stdout, hosts)\n\t_ = hosts.Close()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif hosts, err = dos.Open(context.Background(), \"/etc/example.conf\"); err != nil {\n\t\tif errors.Is(err, fs.ErrNotExist) {\n\t\t\tfmt.Println(\"file does not exist\")\n\t\t} else {\n\t\t\tfmt.Println(err)\n\t\t}\n\t}\n\t// Output:\n\t// example = conf\n\t// file does not exist\n}\n"
  },
  {
    "path": "pkg/dos/lockedfile.go",
    "content": "package dos\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io/fs\"\n\t\"os\"\n\n\t\"github.com/rogpeppe/go-internal/lockedfile\"\n)\n\ntype lockedFs struct {\n\tosFs\n}\n\nfunc WithLockedFs(ctx context.Context) context.Context {\n\treturn WithFS(ctx, &lockedFs{osFs: newOS(ctx)})\n}\n\nfunc (fs *lockedFs) Create(name string) (File, error) {\n\tf, err := lockedfile.Create(name)\n\tif err != nil {\n\t\t// It's important to return a File nil here, not a File that represents an *os.File nil.\n\t\treturn nil, err\n\t}\n\treturn fs.chownFile(f)\n}\n\nfunc (*lockedFs) Open(name string) (File, error) {\n\tf, err := lockedfile.Open(name)\n\tif err != nil {\n\t\t// It's important to return a File nil here, not a File that represents an *os.File nil.\n\t\treturn nil, err\n\t}\n\treturn f, nil\n}\n\nfunc (fs *lockedFs) OpenFile(name string, flag int, perm fs.FileMode) (File, error) {\n\tif fs.mustChown() {\n\t\tif (flag & os.O_CREATE) == os.O_CREATE {\n\t\t\tif _, err := os.Stat(name); errors.Is(err, os.ErrNotExist) {\n\t\t\t\tf, err := lockedfile.OpenFile(name, flag, perm)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn fs.chownFile(f)\n\t\t\t}\n\t\t}\n\t}\n\tf, err := lockedfile.OpenFile(name, flag, perm)\n\tif err != nil {\n\t\t// It's important to return a File nil here, not a File that represents an *os.File nil.\n\t\treturn nil, err\n\t}\n\treturn f, nil\n}\n"
  },
  {
    "path": "pkg/dos/package.go",
    "content": "// Package dos contains an abstraction of some functions in the go os package. When used in code, it allows those\n// functions to be mocked in unit tests.\n// In general, the functions are implemented using an interface which is then stored in the context. The functions\n// are then called using dos instead of os, and with an additional first context argument. E.g.\n//\n//\tctx := dos.WithFS(ctx, mockFS)\n//\tf, err := dos.Open(ctx, \"/etc/resolv.conf\")\npackage dos\n"
  },
  {
    "path": "pkg/dos/stdio.go",
    "content": "package dos\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n)\n\ntype Stdio interface {\n\tInOrStdin() io.Reader\n\tOutOrStdout() io.Writer\n\tErrOrStderr() io.Writer\n}\n\n// WithStdio returns a new context that is parented by the given context, that will return the values\n// from the given Stdio when used as an argument to in a call to Stdin, Stdout, or Stderr.\nfunc WithStdio(ctx context.Context, io Stdio) context.Context {\n\tctx = WithStdin(ctx, io.InOrStdin())\n\tctx = WithStdout(ctx, io.OutOrStdout())\n\treturn WithStderr(ctx, io.ErrOrStderr())\n}\n\ntype outKey struct{}\n\n// Stdout returns the context's stdout io.Writer. If no such writer has been defined, it returns os.Stdout.\nfunc Stdout(ctx context.Context) io.Writer {\n\tif w, ok := ctx.Value(outKey{}).(io.Writer); ok {\n\t\treturn w\n\t}\n\treturn os.Stdout\n}\n\n// WithStdout returns a new context that is parented by the given context, that will return the given writer\n// when used as an argument to in a call to Stdout.\nfunc WithStdout(ctx context.Context, w io.Writer) context.Context {\n\treturn context.WithValue(ctx, outKey{}, w)\n}\n\ntype errKey struct{}\n\n// Stderr returns the context's stdout io.Writer. If no such writer has been defined, it returns os.Stderr.\nfunc Stderr(ctx context.Context) io.Writer {\n\tif w, ok := ctx.Value(errKey{}).(io.Writer); ok {\n\t\treturn w\n\t}\n\treturn os.Stderr\n}\n\n// WithStderr returns a new context that is parented by the given context, that will return the given writer\n// when used as an argument to in a call to Stderr.\nfunc WithStderr(ctx context.Context, w io.Writer) context.Context {\n\treturn context.WithValue(ctx, errKey{}, w)\n}\n\ntype inKey struct{}\n\n// Stdin returns the context's stdin io.Reader. If no such reader has been defined, it returns os.Stdin.\nfunc Stdin(ctx context.Context) io.Reader {\n\tif r, ok := ctx.Value(inKey{}).(io.Reader); ok {\n\t\treturn r\n\t}\n\treturn os.Stdin\n}\n\n// WithStdin returns a new context that is parented by the given context, that will return the given reader\n// when used as an argument to in a call to Stdin.\nfunc WithStdin(ctx context.Context, w io.Reader) context.Context {\n\treturn context.WithValue(ctx, inKey{}, w)\n}\n"
  },
  {
    "path": "pkg/dos/wdwrapper.go",
    "content": "package dos\n\nimport (\n\t\"errors\"\n\t\"io/fs\"\n\t\"path/filepath\"\n)\n\ntype wdWrapper struct {\n\tbase FileSystem\n\twd   string\n}\n\nfunc WorkingDirWrapper(f FileSystem) FileSystem {\n\treturn &wdWrapper{base: f, wd: \"/\"}\n}\n\nfunc (w *wdWrapper) basePath(path string) string {\n\tif filepath.IsAbs(path) {\n\t\treturn path\n\t}\n\treturn filepath.Clean(filepath.Join(w.wd, path))\n}\n\nfunc (w *wdWrapper) Abs(name string) (string, error) {\n\treturn w.basePath(name), nil\n}\n\nfunc (w *wdWrapper) Chdir(path string) error {\n\tpath = w.basePath(path)\n\tif s, err := w.base.Stat(path); err != nil {\n\t\treturn err\n\t} else if !s.IsDir() {\n\t\treturn errors.New(\"not a directory\")\n\t}\n\tw.wd = path\n\treturn nil\n}\n\nfunc (w *wdWrapper) Create(name string) (File, error) {\n\treturn w.base.Create(w.basePath(name))\n}\n\nfunc (w *wdWrapper) Getwd() (string, error) {\n\treturn w.wd, nil\n}\n\nfunc (w *wdWrapper) Mkdir(name string, perm fs.FileMode) error {\n\treturn w.base.Mkdir(w.basePath(name), perm)\n}\n\nfunc (w *wdWrapper) MkdirAll(name string, perm fs.FileMode) error {\n\treturn w.base.MkdirAll(w.basePath(name), perm)\n}\n\nfunc (w *wdWrapper) Open(name string) (File, error) {\n\treturn w.base.Open(w.basePath(name))\n}\n\nfunc (w *wdWrapper) OpenFile(name string, flag int, perm fs.FileMode) (File, error) {\n\treturn w.base.OpenFile(w.basePath(name), flag, perm)\n}\n\nfunc (w *wdWrapper) ReadDir(name string) ([]fs.DirEntry, error) {\n\treturn w.base.ReadDir(w.basePath(name))\n}\n\nfunc (w *wdWrapper) ReadFile(name string) ([]byte, error) {\n\treturn w.base.ReadFile(w.basePath(name))\n}\n\nfunc (w *wdWrapper) RealPath(name string) (string, error) {\n\treturn w.base.RealPath(w.basePath(name))\n}\n\nfunc (w *wdWrapper) Remove(name string) error {\n\treturn w.base.Remove(w.basePath(name))\n}\n\nfunc (w *wdWrapper) RemoveAll(name string) error {\n\treturn w.base.RemoveAll(w.basePath(name))\n}\n\nfunc (w *wdWrapper) Rename(oldName, newName string) error {\n\treturn w.base.Rename(w.basePath(oldName), w.basePath(newName))\n}\n\nfunc (w *wdWrapper) Stat(name string) (fs.FileInfo, error) {\n\treturn w.base.Stat(w.basePath(name))\n}\n\nfunc (w *wdWrapper) Symlink(oldName, newName string) error {\n\treturn w.base.Symlink(w.basePath(oldName), w.basePath(newName))\n}\n\nfunc (w *wdWrapper) WriteFile(name string, data []byte, perm fs.FileMode) error {\n\treturn w.base.WriteFile(w.basePath(name), data, perm)\n}\n"
  },
  {
    "path": "pkg/dpipe/dpipe.go",
    "content": "package dpipe\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"os/exec\" //nolint:depguard // We want no logging and no soft-context signal handling\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n)\n\nfunc DPipe(ctx context.Context, peer io.ReadWriteCloser, cmdName string, cmdArgs ...string) error {\n\tdefer func() {\n\t\t_ = peer.Close()\n\t}()\n\n\tcmd := exec.CommandContext(ctx, cmdName, cmdArgs...)\n\tcmd.Stdin = peer\n\tcmd.Stdout = peer\n\tcmd.Stderr = clog.StdLogger(ctx, slog.LevelError).Writer()\n\n\tcmdLine := shellquote.ShellString(cmd.Path, cmd.Args)\n\tif err := cmd.Start(); err != nil {\n\t\treturn fmt.Errorf(\"failed to start %s: %w\", cmdLine, err)\n\t}\n\n\tctx = clog.With(ctx, \"exec.pid\", cmd.Process.Pid)\n\tclog.Infof(ctx, \"started command %s\", cmdLine)\n\tdefer clog.Infof(ctx, \"ended command %s\", cmdName)\n\trunFinished := make(chan error)\n\tgo func() {\n\t\tdefer close(runFinished)\n\t\tif err := cmd.Wait(); err != nil {\n\t\t\tif !cmd.ProcessState.Success() && ctx.Err() == nil {\n\t\t\t\trunFinished <- err\n\t\t\t}\n\t\t}\n\t}()\n\n\tselect {\n\tcase <-ctx.Done():\n\t\tkillProcess(ctx, cmd)\n\t\treturn nil\n\tcase err := <-runFinished:\n\t\treturn err\n\t}\n}\n"
  },
  {
    "path": "pkg/dpipe/dpipe_test.go",
    "content": "package dpipe\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"os/exec\" //nolint:depguard // This short script has no logging and no Contexts.\n\t\"runtime\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/handler\"\n)\n\nvar echoBinary string\n\nfunc TestMain(m *testing.M) {\n\tebf, err := os.CreateTemp(\"\", \"echo\")\n\tif err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\techoBinary = ebf.Name()\n\tif runtime.GOOS == \"windows\" {\n\t\techoBinary += \".exe\"\n\t}\n\tebf.Close()\n\tif err = exec.Command(\"go\", \"build\", \"-o\", echoBinary, \"./testdata/echo\").Run(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\tdefer os.Remove(echoBinary)\n\tm.Run()\n}\n\ntype bufClose struct {\n\tbytes.Buffer\n}\n\nfunc (b *bufClose) Close() error {\n\treturn nil\n}\n\nfunc makeLoggerOn(bf *bytes.Buffer) context.Context {\n\treturn clog.WithLogger(context.Background(), slog.New(handler.NewText(handler.Output(bf))))\n}\n\n// Test that stdout of a process executed by DPipe is sent to peer.\nfunc TestDPipe_stdout(t *testing.T) {\n\tlog := &bytes.Buffer{}\n\tctx := makeLoggerOn(log)\n\tpeer := &bufClose{}\n\tassert.NoError(t, DPipe(ctx, peer, echoBinary, \"-d\", \"1\", \"hello stdout\"))\n\tassert.Empty(t, log)\n\tassert.Equal(t, \"hello stdout\\n\", peer.String())\n}\n\n// Test that stderr of a process executed by DPipe is logged as errors.\nfunc TestDPipe_stderr(t *testing.T) {\n\tlog := &bytes.Buffer{}\n\tctx := makeLoggerOn(log)\n\tpeer := &bufClose{}\n\tassert.NoError(t, DPipe(ctx, peer, echoBinary, \"-d\", \"2\", \"hello stderr\"))\n\ttime.Sleep(time.Second)\n\tassert.Contains(t, log.String(), `ERROR hello stderr`)\n\tassert.Empty(t, peer.String())\n}\n"
  },
  {
    "path": "pkg/dpipe/dpipe_unix.go",
    "content": "//go:build !windows\n\npackage dpipe\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/exec\" //nolint:depguard // We want no logging and no soft-context signal handling\n\t\"time\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc killProcess(_ context.Context, cmd *exec.Cmd) {\n\t// A process is sometimes not terminated gracefully by the SIGTERM, so we give\n\t// it a second to succeed and then kill it forcefully.\n\ttime.AfterFunc(time.Second, func() {\n\t\tif cmd.ProcessState == nil {\n\t\t\t_ = cmd.Process.Signal(unix.SIGKILL)\n\t\t}\n\t})\n\t_ = cmd.Process.Signal(os.Interrupt)\n}\n"
  },
  {
    "path": "pkg/dpipe/dpipe_windows.go",
    "content": "package dpipe\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/exec\" //nolint:depguard // We want no logging and no soft-context signal handling\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\nfunc killProcess(ctx context.Context, cmd *exec.Cmd) {\n\tproc.KillProcessGroup(ctx, cmd, os.Kill)\n}\n"
  },
  {
    "path": "pkg/dpipe/testdata/echo/echo.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n)\n\nfunc main() {\n\tvar dest string\n\tflag.StringVar(&dest, \"d\", \"1\", \"Destination of output. Legal values are 1 (stdout), 2 (stderr) or a file name\")\n\tflag.Parse()\n\n\tvar out *os.File\n\tswitch dest {\n\tcase \"1\":\n\t\tout = os.Stdout\n\tcase \"2\":\n\t\tout = os.Stderr\n\tdefault:\n\t\tvar err error\n\t\tif out, err = os.Create(dest); err != nil {\n\t\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\t\tos.Exit(1)\n\t\t}\n\t\tdefer out.Close()\n\t}\n\tfor _, s := range flag.Args() {\n\t\tfmt.Fprintln(out, s)\n\t}\n}\n"
  },
  {
    "path": "pkg/errcat/errors.go",
    "content": "package errcat\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n)\n\n// The Category is used for categorizing errors so that we can know when\n// to point the user to the logs or not.\ntype Category int\n\ntype categorized struct {\n\terror\n\tcategory Category\n}\n\ntype Categorized interface {\n\terror\n\n\t// GetCategory returns the error category for a categorized error.\n\tGetCategory() Category\n}\n\nconst (\n\tOK           = Category(iota)\n\tUser         // User made an error\n\tConfig       // Errors in config.yml, extensions, or kubeconfig\n\tNoDaemonLogs // Other error generated in the CLI process, so no use pointing the user to logs\n\tSilent       // Don't print error on exit, it has already been conveyed to the user\n\tUnknown      // Something else. Consult the logs\n)\n\n// New creates a new categorized error based in its argument. The argument\n// can be an error or a string. If it isn't, it will be converted to a string\n// using its '%v' formatter.\nfunc (c Category) New(untypedErr any) error {\n\tvar err error\n\tswitch untypedErr := untypedErr.(type) {\n\tcase nil:\n\t\treturn nil\n\tcase *categorized:\n\t\treturn untypedErr\n\tcase error:\n\t\terr = untypedErr\n\tcase string:\n\t\terr = errors.New(untypedErr)\n\tdefault:\n\t\terr = fmt.Errorf(\"%v\", untypedErr)\n\t}\n\treturn &categorized{error: err, category: c}\n}\n\n// Newf creates a new categorized error based on a format string with arguments. The\n// error is created using fmt.Errorf() so using '%w' is relevant for error arguments.\nfunc (c Category) Newf(format string, a ...any) error {\n\treturn &categorized{error: fmt.Errorf(format, a...), category: c}\n}\n\n// Errorf creates a new categorized error based on a format string with arguments. The\n// format string will get \": %w\" appended to it, and the given error will be appended\n// to the args before the returned error is created using fmt.Errorf().\nfunc (c Category) Errorf(err error, format string, a ...any) error {\n\treturn &categorized{error: fmt.Errorf(format+\": %w\", append(a, err)...), category: c}\n}\n\n// Print prints error on dos.Stderr(ctx) unless it is nil or Silent.\nfunc Print(err error) {\n\tswitch GetCategory(err) {\n\tcase OK, Silent:\n\tdefault:\n\t\tioutil.Println(os.Stderr, err.Error())\n\t}\n}\n\n// GetCategory returns the error category for a categorized error.\nfunc (ce *categorized) GetCategory() Category {\n\treturn ce.category\n}\n\n// Unwrap this categorized error.\nfunc (ce *categorized) Unwrap() error {\n\treturn ce.error\n}\n\n// GetCategory returns the error category for a categorized error, OK for nil, and\n// Unknown for other errors.\nfunc GetCategory(err error) Category {\n\tif err == nil {\n\t\treturn OK\n\t}\n\t// Keep unwrapping until a category is found (or not)\n\tfor {\n\t\tvar ce Categorized\n\t\tif errors.As(err, &ce) {\n\t\t\treturn ce.GetCategory()\n\t\t}\n\t\tif err = errors.Unwrap(err); err == nil {\n\t\t\treturn Unknown\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/filelocation/app.go",
    "content": "package filelocation\n\nimport (\n\t\"context\"\n\t\"path/filepath\"\n)\n\n// AppUserLogDir returns the directory to use for application-specific\n// user-specific log files.\n//\n//   - On Darwin, it returns \"$HOME/Library/Logs/telepresence\".  Specified by:\n//     https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/MacOSXDirectories/MacOSXDirectories.html\n//\n//   - On everything else, it returns \"{{AppUserCacheDir}}/logs\" (using the\n//     appropriate path separator, if not \"/\").\n//\n// If the location cannot be determined (for example, $HOME is not defined),\n// then it will return an error.\nfunc AppUserLogDir(ctx context.Context) string {\n\tif logDir, ok := ctx.Value(logCtxKey{}).(string); ok && logDir != \"\" {\n\t\treturn logDir\n\t}\n\treturn appUserLogDir(ctx)\n}\n\n// AppUserCacheDir returns the directory to use for application-specific\n// user-specific cache data.\n//\n// On all platforms, this returns \"{{UserCacheDir}}/telepresence\" (using the\n// appropriate path separator, if not \"/\").\n//\n// If the location cannot be determined (for example, $HOME is not defined),\n// then it will return an error.\nfunc AppUserCacheDir(ctx context.Context) string {\n\tif cacheDir, ok := ctx.Value(cacheCtxKey{}).(string); ok && cacheDir != \"\" {\n\t\treturn cacheDir\n\t}\n\treturn filepath.Join(UserCacheDir(ctx), appName)\n}\n\n// AppUserConfigDir returns the directory to use for application-specific\n// user-specific configuration data.\n//\n// On all platforms, this returns \"{{UserConfigDir}}/telepresence\" (using the\n// appropriate path separator, if not \"/\").\n//\n// If the location cannot be determined (for example, $HOME is not defined),\n// then it will return an error.\nfunc AppUserConfigDir(ctx context.Context) string {\n\tif configDir, ok := ctx.Value(configCtxKey{}).(string); ok && configDir != \"\" {\n\t\treturn configDir\n\t}\n\treturn filepath.Join(UserConfigDir(ctx), appName)\n}\n"
  },
  {
    "path": "pkg/filelocation/ctx.go",
    "content": "package filelocation\n\nimport (\n\t\"context\"\n)\n\ntype homeCtxKey struct{}\n\n// WithUserHomeDir spoofs the UserHomedir and all derived values for all functions in this package.\n// This is useful for testing and should not be used in the normal code.\nfunc WithUserHomeDir(ctx context.Context, home string) context.Context {\n\treturn context.WithValue(ctx, homeCtxKey{}, home)\n}\n\ntype logCtxKey struct{}\n\n// WithAppUserLogDir spoofs the AppUserLogDir.  This is useful for testing or for when logging to a\n// normal user's logs as root.\nfunc WithAppUserLogDir(ctx context.Context, logdir string) context.Context {\n\treturn context.WithValue(ctx, logCtxKey{}, logdir)\n}\n\ntype configCtxKey struct{}\n\n// WithAppUserConfigDir spoofs the AppUserConfigDir.  This is useful for testing.\nfunc WithAppUserConfigDir(ctx context.Context, configDir string) context.Context {\n\treturn context.WithValue(ctx, configCtxKey{}, configDir)\n}\n\ntype cacheCtxKey struct{}\n\n// WithAppUserCacheDir spoofs the AppUserCacheDir.  This is useful for testing.\nfunc WithAppUserCacheDir(ctx context.Context, cacheDir string) context.Context {\n\treturn context.WithValue(ctx, cacheCtxKey{}, cacheDir)\n}\n"
  },
  {
    "path": "pkg/filelocation/osfile.go",
    "content": "package filelocation\n\nimport (\n\t\"context\"\n)\n\n// UserHomeDir returns the user's home directory path. It first checks for a context override\n// value, otherwise it delegates to an OS-specific implementation that retrieves the home directory\n// from environment variables ($HOME on Unix-like systems, %USERPROFILE% on Windows).\nfunc UserHomeDir(ctx context.Context) string {\n\tif override, ok := ctx.Value(homeCtxKey{}).(string); ok {\n\t\treturn override\n\t}\n\treturn userHomeDir()\n}\n\n// UserCacheDir returns the default root directory to use for user-specific cached data.\n// It delegates to an OS-specific implementation that returns:\n//   - On Unix-like systems: $XDG_CACHE_HOME if set, otherwise $HOME/.cache\n//   - On macOS: $HOME/Library/Caches\n//   - On Windows: %LocalAppData% if set, otherwise %USERPROFILE%\\AppData\\Local\nfunc UserCacheDir(ctx context.Context) string {\n\treturn userCacheDir(ctx)\n}\n\n// UserConfigDir returns the default root directory to use for user-specific configuration data.\n// It delegates to an OS-specific implementation that returns:\n//   - On Unix-like systems: $XDG_CONFIG_HOME if set, otherwise $HOME/.config\n//   - On macOS: $HOME/Library/Application Support\n//   - On Windows: %AppData% if set, otherwise %USERPROFILE%\\AppData\\Roaming\nfunc UserConfigDir(ctx context.Context) string {\n\treturn userConfigDir(ctx)\n}\n"
  },
  {
    "path": "pkg/filelocation/osfile_darwin.go",
    "content": "package filelocation\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\tappName       = \"telepresence\"\n\tRootCacheDir  = \"/Library/Caches/\" + appName\n\tRootConfigDir = \"/Library/Application Support/\" + appName\n)\n\nfunc userHomeDir() string {\n\tif v := os.Getenv(\"HOME\"); v != \"\" {\n\t\treturn v\n\t}\n\tpanic(\"$HOME is not defined\")\n}\n\nfunc userCacheDir(ctx context.Context) string {\n\treturn filepath.Join(UserHomeDir(ctx), \"Library\", \"Caches\")\n}\n\nfunc userConfigDir(ctx context.Context) string {\n\treturn filepath.Join(UserHomeDir(ctx), \"Library\", \"Application Support\")\n}\n\nfunc appUserLogDir(ctx context.Context) string {\n\treturn filepath.Join(UserHomeDir(ctx), \"Library\", \"Logs\", appName)\n}\n"
  },
  {
    "path": "pkg/filelocation/osfile_linux.go",
    "content": "package filelocation\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\tappName       = \"telepresence\"\n\tRootCacheDir  = \"/var/cache/\" + appName\n\tRootConfigDir = \"/etc/\" + appName\n)\n\nfunc userHomeDir() string {\n\tif v := os.Getenv(\"HOME\"); v != \"\" {\n\t\treturn v\n\t}\n\tpanic(\"$HOME is not defined\")\n}\n\nfunc userCacheDir(ctx context.Context) string {\n\tdir := os.Getenv(\"XDG_CACHE_HOME\")\n\tif dir == \"\" {\n\t\tdir = filepath.Join(UserHomeDir(ctx), \".cache\")\n\t}\n\treturn dir\n}\n\nfunc userConfigDir(ctx context.Context) string {\n\tdir := os.Getenv(\"XDG_CONFIG_HOME\")\n\tif dir == \"\" {\n\t\tdir = filepath.Join(UserHomeDir(ctx), \".config\")\n\t}\n\treturn dir\n}\n\nfunc appUserLogDir(ctx context.Context) string {\n\treturn filepath.Join(AppUserCacheDir(ctx), \"logs\")\n}\n"
  },
  {
    "path": "pkg/filelocation/osfile_windows.go",
    "content": "package filelocation\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\tappName       = \"Telepresence\"\n\tRootConfigDir = \"C:\\\\ProgramData\\\\\" + appName\n\tRootCacheDir  = RootConfigDir + \"\\\\Cache\"\n)\n\nfunc userHomeDir() string {\n\tif v := os.Getenv(\"USERPROFILE\"); v != \"\" {\n\t\treturn v\n\t}\n\tpanic(\"%userprofile% is not defined\")\n}\n\nfunc userCacheDir(ctx context.Context) string {\n\tdir := os.Getenv(\"LocalAppData\")\n\tif dir == \"\" {\n\t\tdir = filepath.Join(UserHomeDir(ctx), \"AppData\", \"Local\")\n\t}\n\treturn dir\n}\n\nfunc userConfigDir(ctx context.Context) string {\n\tdir := os.Getenv(\"AppData\")\n\tif dir == \"\" {\n\t\tdir = filepath.Join(UserHomeDir(ctx), \"AppData\", \"Roaming\")\n\t}\n\treturn dir\n}\n\nfunc appUserLogDir(ctx context.Context) string {\n\treturn filepath.Join(AppUserCacheDir(ctx), \"Logs\")\n}\n"
  },
  {
    "path": "pkg/forwarder/basic.go",
    "content": "package forwarder\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// A Forwarder forwards TCP or UDP connections.\ntype Forwarder interface {\n\t// Forward from the given client connection to the target host:port of this forwarder.\n\t// The client connection will be closed after the forwarding is complete.\n\tForward(ctx context.Context, clientConn net.Conn) error\n\n\t// Listen returns a listener that will listen for connections to the port specified in the forwarder and\n\t// updates the port in the forwarder in case the port was initially zero.\n\t// Listen is only implemented for TCP.\n\tListen(ctx context.Context) (net.Listener, error)\n\n\t// ListenPort returns the port that this forwarder will listen to. This port will be updated\n\t// by a call to Serve if the port was initially zero.\n\tListenPort() types.PortAndProto\n\n\t// Serve will call the ServeTCP or ServeUDP method depending on the protocol of the forwarder.\n\tServe(ctx context.Context, initCh chan<- netip.AddrPort) error\n\n\t// ServeTo starts the listener and accept-loop for this forwarder. The listener will listen to all available addresses on the given port.\n\t// The accept-loop calls the fw function a separate go-routine for each accepted connection.\n\t// The fw function is responsible for closing the connection.\n\t// The port can be zero, in which case the listener will assign a random port number.\n\t// The port number can be retrieved via the ListenPort method.\n\tServeTo(ctx context.Context, initCh chan<- netip.AddrPort, fw func(context.Context, net.Conn) error) error\n\n\t// Tag used for logging purposes.\n\tTag() tunnel.Tag\n\n\t// Target returns the target host:port that this forwarder forwards to.\n\tTarget() netip.AddrPort\n}\n\ntype basic struct {\n\ttag        tunnel.Tag\n\ttarget     netip.AddrPort\n\tlistenPort int32\n}\n\n// New creates a TCP or UDP forwarder that will forward connections from the given port to the given target.\nfunc New(from types.PortAndProto, tag tunnel.Tag, target netip.AddrPort) Forwarder {\n\tif from.Proto == types.ProtoUDP {\n\t\treturn NewUDP(from.Port, tag, target)\n\t}\n\treturn NewTCP(from.Port, tag, target)\n}\n\nfunc (f *basic) Tag() tunnel.Tag {\n\treturn f.tag\n}\n\nfunc (f *basic) Target() netip.AddrPort {\n\treturn f.target\n}\n"
  },
  {
    "path": "pkg/forwarder/tcp.go",
    "content": "package forwarder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"sync/atomic\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype tcp struct{ basic }\n\n// NewTCP creates a new TCP forwarder that will forward connections from the given port to the given target.\nfunc NewTCP(from uint16, tag tunnel.Tag, target netip.AddrPort) Forwarder {\n\treturn &tcp{basic{\n\t\ttag:        tag,\n\t\ttarget:     target,\n\t\tlistenPort: int32(from),\n\t}}\n}\n\nfunc (f *tcp) Forward(ctx context.Context, clientConn net.Conn) error {\n\tdefer clog.Debug(ctx, \"Done forwarding\")\n\tdefer clientConn.Close()\n\n\tif f.target.Port() == 0 {\n\t\tclog.Debug(ctx, \"Forwarding to /dev/null\")\n\t\t_, _ = io.Copy(io.Discard, clientConn)\n\t\treturn nil\n\t}\n\n\tctx = clog.With(ctx, \"target\", f.target)\n\n\tclog.Debug(ctx, \"Forwarding...\")\n\ttargetConn, err := net.DialTCP(\"tcp\", nil, net.TCPAddrFromAddrPort(f.target))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error on dial: %w\", err)\n\t}\n\tdefer targetConn.Close()\n\n\tdone := make(chan struct{})\n\n\tgo func() {\n\t\tif _, err := io.Copy(targetConn, clientConn); err != nil && ctx.Err() == nil {\n\t\t\tclog.Debugf(ctx, \"Error clientConn->targetConn: %+v\", err)\n\t\t}\n\t\t_ = targetConn.CloseWrite()\n\t\tdone <- struct{}{}\n\t}()\n\tgo func() {\n\t\tif _, err := io.Copy(clientConn, targetConn); err != nil && ctx.Err() == nil {\n\t\t\tclog.Debugf(ctx, \"Error targetConn->clientConn: %+v\", err)\n\t\t}\n\t\tif hwCloser, ok := clientConn.(interface{ CloseWrite() error }); ok {\n\t\t\t_ = hwCloser.CloseWrite()\n\t\t}\n\t\tdone <- struct{}{}\n\t}()\n\n\t// Wait for both sides to close the connection\n\tfor numClosed := 0; numClosed < 2; {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tcase <-done:\n\t\t\tnumClosed++\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (f *tcp) Listen(ctx context.Context) (net.Listener, error) {\n\tlc := net.ListenConfig{}\n\tlistener, err := lc.Listen(ctx, \"tcp\", fmt.Sprintf(\":%d\", atomic.LoadInt32(&f.listenPort)))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tatomic.StoreInt32(&f.listenPort, int32(listener.Addr().(*net.TCPAddr).AddrPort().Port()))\n\treturn listener, nil\n}\n\n// ListenPort returns the port that this forwarder will listen to. This port will be updated\n// by a call to Serve if the port was initially zero.\nfunc (f *basic) ListenPort() types.PortAndProto {\n\treturn types.PortAndProto{Proto: types.ProtoTCP, Port: uint16(atomic.LoadInt32(&f.listenPort))}\n}\n\nfunc (f *tcp) Serve(ctx context.Context, port chan<- netip.AddrPort) error {\n\treturn f.ServeTo(ctx, port, f.Forward)\n}\n\nfunc (f *tcp) ServeTo(ctx context.Context, initCh chan<- netip.AddrPort, fw func(context.Context, net.Conn) error) error {\n\tlistener, err := f.Listen(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\tgo func() {\n\t\t<-ctx.Done()\n\t\tlistener.Close()\n\t}()\n\tif initCh != nil {\n\t\tinitCh <- listener.Addr().(*net.TCPAddr).AddrPort()\n\t\tclose(initCh)\n\t}\n\tAcceptLoop(ctx, listener, fw)\n\treturn nil\n}\n\n// AcceptLoop accepts connections on the listener and calls f in a separate go routine for each connection.\nfunc AcceptLoop(ctx context.Context, listener net.Listener, fw func(context.Context, net.Conn) error) {\n\tfor {\n\t\tconn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tclog.Infof(ctx, \"listener.Accept ended with error: %v\", err)\n\t\t\t}\n\t\t}\n\t\tgo func() {\n\t\t\tif err := fw(ctx, conn); err != nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t}()\n\t}\n}\n"
  },
  {
    "path": "pkg/forwarder/udp.go",
    "content": "package forwarder\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"sync/atomic\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype udp struct{ basic }\n\n// NewUDP creates a new UDP forwarder that will forward connections from the given port to the given target.\nfunc NewUDP(from uint16, tag tunnel.Tag, target netip.AddrPort) Forwarder {\n\treturn &udp{basic{\n\t\ttag:        tag,\n\t\ttarget:     target,\n\t\tlistenPort: int32(from),\n\t}}\n}\n\nfunc (f *udp) Listen(ctx context.Context) (net.Listener, error) {\n\treturn nil, fmt.Errorf(\"listen is not implemented for UDP\")\n}\n\n// ListenPort returns the port that this forwarder will listen to. This port will be updated\n// by a call to Serve if the port was initially zero.\nfunc (f *udp) ListenPort() types.PortAndProto {\n\treturn types.PortAndProto{Proto: types.ProtoUDP, Port: uint16(atomic.LoadInt32(&f.listenPort))}\n}\n\nfunc (f *udp) Serve(ctx context.Context, initCh chan<- netip.AddrPort) error {\n\treturn f.ServeTo(ctx, initCh, f.Forward)\n}\n\nfunc (f *udp) Forward(ctx context.Context, conn net.Conn) error {\n\tif udpConn, ok := conn.(*net.UDPConn); ok {\n\t\treturn ForwardUDP(ctx, f.tag, udpConn, f.target)\n\t}\n\treturn fmt.Errorf(\"not a UDP connection\")\n}\n\nfunc (f *udp) ServeTo(ctx context.Context, initCh chan<- netip.AddrPort, fw func(context.Context, net.Conn) error) error {\n\t// Set up listener lifetime (same as the overall forwarder lifetime)\n\tlp := uint16(atomic.LoadInt32(&f.listenPort))\n\tdefer func() {\n\t\tif initCh != nil {\n\t\t\tclose(initCh)\n\t\t}\n\t\tclog.Infof(ctx, \"Done forwarding udp from :%d\", lp)\n\t}()\n\n\tfor first := true; ; first = false {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tdefault:\n\t\t}\n\t\tlc := net.ListenConfig{}\n\t\tpc, err := lc.ListenPacket(ctx, \"udp\", fmt.Sprintf(\":%d\", lp))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif first {\n\t\t\t// The address to listen to is likely to change the first time around, because it may\n\t\t\t// be \":0\", so let's ensure that the same address is used next time\n\t\t\tla := pc.LocalAddr().(*net.UDPAddr)\n\t\t\tatomic.StoreInt32(&f.listenPort, int32(la.Port))\n\t\t\tclog.Infof(ctx, \"Forwarding udp from %s\", la)\n\t\t\tif initCh != nil {\n\t\t\t\tinitCh <- la.AddrPort()\n\t\t\t\tclose(initCh)\n\t\t\t\tinitCh = nil\n\t\t\t}\n\t\t}\n\t\terr = fw(ctx, pc.(*net.UDPConn))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n}\n\n// ForwardUDP reads packets from the given connection and writes the packages to the\n// target host:port of this forwarder using a connection that will use the reply address\n// from the read as the destination for packages going in the other direction.\nfunc ForwardUDP(ctx context.Context, tag tunnel.Tag, conn *net.UDPConn, targetAddr netip.AddrPort) error {\n\ttargets := tunnel.NewPool()\n\tla := conn.LocalAddr()\n\tclog.Infof(ctx, \"Forwarding udp from %s to %s\", la, targetAddr)\n\tdefer func() {\n\t\ttargets.CloseAll(ctx)\n\t\t_ = conn.Close()\n\t\tclog.Infof(ctx, \"Done forwarding udp from %s to %s\", la, targetAddr)\n\t}()\n\tif targetAddr.Port() == 0 {\n\t\tclog.Debug(ctx, \"Forwarding to /dev/null\")\n\t\treturn nil\n\t}\n\n\tch := make(chan tunnel.UdpReadResult)\n\tgo tunnel.UdpReader(ctx, tag, conn, ch)\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil\n\t\tcase rr, ok := <-ch:\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tid := tunnel.ConnIDFromUDP(rr.Address, targetAddr)\n\t\t\tclog.Tracef(ctx, \"<- %s udp %s, len %d\", tag, id, len(rr.Payload))\n\t\t\th, _, err := targets.GetOrCreate(ctx, id, func(ctx context.Context, release func()) (tunnel.Handler, error) {\n\t\t\t\ttc, err := net.DialUDP(\"udp\", nil, net.UDPAddrFromAddrPort(id.Destination()))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\treturn &udpHandler{\n\t\t\t\t\ttag:       tag,\n\t\t\t\t\tUDPConn:   tc,\n\t\t\t\t\tid:        id,\n\t\t\t\t\treplyWith: conn,\n\t\t\t\t\trelease:   release,\n\t\t\t\t}, nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tuh := h.(*udpHandler)\n\t\t\tpn := len(rr.Payload)\n\t\t\tfor n := 0; n < pn; {\n\t\t\t\twn, err := uh.Write(rr.Payload[n:])\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"!> %s udp %s write: %v\", tag, id, err)\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tclog.Tracef(ctx, \"-> %s udp %s, len %d\", tag, id, wn)\n\t\t\t\tn += wn\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype udpHandler struct {\n\t*net.UDPConn\n\tid        tunnel.ConnID\n\treplyWith net.PacketConn\n\ttag       tunnel.Tag\n\trelease   func()\n}\n\nfunc (u *udpHandler) Close() error {\n\tu.release()\n\treturn u.UDPConn.Close()\n}\n\nfunc (u *udpHandler) Stop(_ context.Context) {\n\t_ = u.Close()\n}\n\nfunc (u *udpHandler) Start(ctx context.Context) {\n\tgo u.forward(ctx, u.tag)\n}\n\nfunc (u *udpHandler) forward(ctx context.Context, tag tunnel.Tag) {\n\tch := make(chan tunnel.UdpReadResult)\n\tgo tunnel.UdpReader(ctx, tag, u, ch)\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase rr, ok := <-ch:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpn := len(rr.Payload)\n\t\t\tfor n := 0; n < pn; {\n\t\t\t\twn, err := u.replyWith.WriteTo(rr.Payload[n:], net.UDPAddrFromAddrPort(u.id.Source()))\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"!> %s udp %s write: %v\", tag, u.id, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tclog.Tracef(ctx, \"-> %s udp %s, len %d\", tag, u.id, wn)\n\t\t\t\tn += wn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/grpc/client/connect.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/connectivity\"\n)\n\nfunc DialGRPC(ctx context.Context, addr string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {\n\tconn, err := grpc.NewClient(addr, opts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstate := conn.GetState()\n\tconn.Connect()\n\n\t// Wait for the connection to reach READY state or fail\n\tfor {\n\t\tswitch state {\n\t\tcase connectivity.Ready:\n\t\t\t// Connection is established\n\t\t\treturn conn, nil\n\t\tcase connectivity.Shutdown:\n\t\t\tconn.Close()\n\t\t\treturn nil, fmt.Errorf(\"connection failed: state=%v\", state)\n\t\tdefault:\n\t\t\tif !conn.WaitForStateChange(ctx, state) {\n\t\t\t\t// Normal. The context timed out before the connection reached READY state.\n\t\t\t\tconn.Close()\n\t\t\t\treturn nil, fmt.Errorf(\"%v: %w\", state, ctx.Err())\n\t\t\t}\n\t\t\tstate = conn.GetState()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/grpc/error.go",
    "content": "package grpc\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\n\tgrpcCodes \"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n)\n\ntype Error interface {\n\tError() string\n\tGRPCStatus() *status.Status\n}\n\ntype StructuredError struct {\n\tCode     grpcCodes.Code  `json:\"code\"`\n\tMessage  string          `json:\"message\"`\n\tCategory errcat.Category `json:\"category\"`\n}\n\ntype structuredGrpcError struct {\n\tStructuredError\n\tstatus *status.Status\n}\n\nfunc (e *StructuredError) GetCode() grpcCodes.Code {\n\treturn e.Code\n}\n\nfunc (e *structuredGrpcError) GRPCStatus() *status.Status {\n\treturn e.status\n}\n\nfunc (e *StructuredError) Error() string {\n\treturn e.Message\n}\n\nfunc (e *StructuredError) GetCategory() errcat.Category {\n\treturn e.Category\n}\n\nfunc FromGRPC(err error) error {\n\tif err != nil {\n\t\tvar grpcErr Error\n\t\tif errors.As(err, &grpcErr) {\n\t\t\tst := grpcErr.GRPCStatus()\n\t\t\tvar es StructuredError\n\t\t\tif json.Unmarshal([]byte(st.Message()), &es) == nil {\n\t\t\t\treturn &es\n\t\t\t} else {\n\t\t\t\treturn &structuredGrpcError{\n\t\t\t\t\tStructuredError: StructuredError{\n\t\t\t\t\t\tCode:     st.Code(),\n\t\t\t\t\t\tMessage:  st.Message(),\n\t\t\t\t\t\tCategory: errcat.Unknown,\n\t\t\t\t\t},\n\t\t\t\t\tstatus: st,\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/grpc/errors/errors.go",
    "content": "package errors\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\tk8sErrors \"k8s.io/apimachinery/pkg/api/errors\"\n)\n\n// Error is like status.Error, but it will never return nil. Instead, it panics if the code is codes.Ok\n//\n// The sole purpose of this function is to remedy https://youtrack.jetbrains.com/issue/GO-19802\nfunc Error(c codes.Code, msg string) error {\n\terr := status.Error(c, msg)\n\tif err == nil {\n\t\tpanic(\"foo\")\n\t}\n\treturn err\n}\n\n// Errorf is like status.Errorf, but it will never return nil. Instead, it panics if the code is codes.Ok\n//\n// The sole purpose of this function is to remedy https://youtrack.jetbrains.com/issue/GO-19802\nfunc Errorf(c codes.Code, format string, a ...any) error {\n\terr := status.Errorf(c, format, a...)\n\tif err == nil {\n\t\tpanic(\"foo\")\n\t}\n\treturn err\n}\n\nfunc FromError(err error, defaultCode codes.Code, defaultMessage string) error {\n\tif err == nil {\n\t\treturn nil\n\t}\n\tst, ok := status.FromError(err)\n\tswitch {\n\tcase ok:\n\t\terr = st.Err()\n\tcase errors.Is(err, context.Canceled):\n\t\terr = status.Error(codes.Canceled, err.Error())\n\tcase errors.Is(err, context.DeadlineExceeded):\n\t\terr = status.Error(codes.DeadlineExceeded, err.Error())\n\tcase errors.Is(err, os.ErrNotExist), k8sErrors.IsNotFound(err):\n\t\terr = status.Error(codes.NotFound, err.Error())\n\tdefault:\n\t\terr = status.Error(defaultCode, defaultMessage)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/grpc/server/context.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"time\"\n)\n\nfunc NewCombinedContext(a, b context.Context) context.Context {\n\treturn &combinedContext{a, b, nil}\n}\n\ntype combinedContext struct {\n\ta   context.Context\n\tb   context.Context\n\terr error\n}\n\nfunc (c *combinedContext) Deadline() (time.Time, bool) {\n\tif dla, ok := c.a.Deadline(); ok {\n\t\tif dlb, ok := c.b.Deadline(); ok {\n\t\t\tif dlb.Before(dla) {\n\t\t\t\treturn dlb, ok\n\t\t\t}\n\t\t}\n\t\treturn dla, ok\n\t}\n\treturn c.b.Deadline()\n}\n\nfunc (c *combinedContext) Done() <-chan struct{} {\n\tdone := make(chan struct{})\n\tgo func() {\n\t\tselect {\n\t\tcase <-c.a.Done():\n\t\t\tc.err = c.a.Err()\n\t\tcase <-c.b.Done():\n\t\t\tc.err = c.b.Err()\n\t\t}\n\t\tclose(done)\n\t}()\n\treturn done\n}\n\nfunc (c *combinedContext) Err() error {\n\treturn c.err\n}\n\nfunc (c *combinedContext) Value(key any) any {\n\tv := c.a.Value(key)\n\tif v == nil {\n\t\tv = c.b.Value(key)\n\t}\n\treturn v\n}\n"
  },
  {
    "path": "pkg/grpc/server/logging.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nfunc interceptorLogger() logging.Logger {\n\treturn logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, fields ...any) {\n\t\tlfs := logging.Fields(fields)\n\t\tattrs := make([]slog.Attr, 0, len(lfs)/2)\n\t\ti := lfs.Iterator()\n\t\tfor i.Next() {\n\t\t\tk, v := i.At()\n\t\t\tswitch k {\n\t\t\tcase logging.MethodFieldKey:\n\t\t\t\t// Don't log the remain ping unless we're tracing\n\t\t\t\tif v == \"Remain\" && !clog.Enabled(ctx, clog.LevelTrace) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\tcase\n\t\t\t\tlogging.ComponentFieldKey,\n\t\t\t\tlogging.ServiceFieldKey,\n\t\t\t\tlogging.MethodTypeFieldKey,\n\t\t\t\t\"grpc.request.deadline\",\n\t\t\t\t\"grpc.start_time\",\n\t\t\t\t\"grpc.time_ms\",\n\t\t\t\t\"peer.address\",\n\t\t\t\t\"protocol\":\n\t\t\tdefault:\n\t\t\t\tattrs = append(attrs, slog.Any(k, v))\n\t\t\t}\n\t\t}\n\t\tswitch lvl {\n\t\tcase logging.LevelDebug:\n\t\t\t// We treat debug logging from GRPC as Trace\n\t\t\tclog.TraceAttrs(ctx, msg, attrs...)\n\t\tcase logging.LevelInfo:\n\t\t\t// We treat info logging from GRPC as Debug\n\t\t\tclog.DebugAttrs(ctx, msg, attrs...)\n\t\tcase logging.LevelWarn:\n\t\t\tclog.WarnAttrs(ctx, msg, attrs...)\n\t\tcase logging.LevelError:\n\t\t\tclog.ErrorAttrs(ctx, msg, attrs...)\n\t\t}\n\t})\n}\n\nfunc callCtx(ctx context.Context, name string, requestCount *uint64) context.Context {\n\tif ix := strings.LastIndexByte(name, '/'); ix >= 0 {\n\t\tname = name[ix+1:]\n\t}\n\tnum := atomic.AddUint64(requestCount, 1)\n\treturn clog.WithGroup(ctx, fmt.Sprintf(\"%s-%d\", name, num))\n}\n"
  },
  {
    "path": "pkg/grpc/server/server.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"log/slog\"\n\t\"net\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\ttpGrpc \"github.com/telepresenceio/telepresence/v2/pkg/grpc\"\n)\n\ntype mergedCtx struct {\n\tcontext.Context\n\tvalCtx context.Context\n}\n\nfunc (m *mergedCtx) Value(i any) any {\n\tif v := m.valCtx.Value(i); v != nil {\n\t\treturn v\n\t}\n\treturn m.Context.Value(i)\n}\n\ntype mergedStream struct {\n\tgrpc.ServerStream\n\tvalCtx context.Context\n}\n\nfunc (s *mergedStream) Context() context.Context {\n\treturn &mergedCtx{Context: s.ServerStream.Context(), valCtx: s.valCtx}\n}\n\nfunc jsonError(err error) error {\n\tvar errStruct *tpGrpc.StructuredError\n\tvar grpcError tpGrpc.Error\n\tswitch {\n\tcase err == nil, errors.As(err, &grpcError):\n\t// Don't touch\n\tcase errors.As(err, &errStruct):\n\t\ts, _ := json.Marshal(errStruct)\n\t\terr = status.Error(errStruct.Code, string(s))\n\tcase errors.Is(err, context.Canceled):\n\t\terr = status.Error(codes.Canceled, err.Error())\n\tcase errors.Is(err, context.DeadlineExceeded):\n\t\terr = status.Error(codes.DeadlineExceeded, err.Error())\n\tdefault:\n\t\tes := &tpGrpc.StructuredError{\n\t\t\tCode:     codes.Internal,\n\t\t\tMessage:  err.Error(),\n\t\t\tCategory: errcat.GetCategory(err),\n\t\t}\n\t\ts, _ := json.Marshal(es)\n\t\terr = status.Error(es.Code, string(s))\n\t}\n\treturn err\n}\n\n// New creates a gRPC server which has no service registered and has not started to accept requests yet. Values\n// in the provided context will be included in the context passed to both unary and stream calls.\nfunc New(valCtx context.Context, options ...grpc.ServerOption) *grpc.Server {\n\trequestCount := uint64(0)\n\tunaryContextInterceptor := func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {\n\t\treturn handler(&mergedCtx{Context: ctx, valCtx: callCtx(valCtx, info.FullMethod, &requestCount)}, req)\n\t}\n\tstreamContextInterceptor := func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\t\treturn handler(srv, &mergedStream{\n\t\t\tServerStream: ss,\n\t\t\tvalCtx:       callCtx(valCtx, info.FullMethod, &requestCount),\n\t\t})\n\t}\n\n\tunaryErrorInterceptor := func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {\n\t\tv, err := handler(ctx, req)\n\t\treturn v, jsonError(err)\n\t}\n\tstreamErrorInterceptor := func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {\n\t\treturn jsonError(handler(srv, ss))\n\t}\n\n\tif clog.Enabled(valCtx, slog.LevelDebug) {\n\t\topts := []logging.Option{\n\t\t\tlogging.WithLogOnEvents(logging.StartCall, logging.FinishCall),\n\t\t\t// Add any other option (check functions starting with logging.With).\n\t\t}\n\t\toptions = append(\n\t\t\toptions,\n\t\t\tgrpc.ChainUnaryInterceptor(\n\t\t\t\tunaryContextInterceptor,\n\t\t\t\tlogging.UnaryServerInterceptor(interceptorLogger(), opts...),\n\t\t\t\tunaryErrorInterceptor,\n\t\t\t),\n\t\t\tgrpc.ChainStreamInterceptor(\n\t\t\t\tstreamContextInterceptor,\n\t\t\t\tlogging.StreamServerInterceptor(interceptorLogger(), opts...),\n\t\t\t\tstreamErrorInterceptor,\n\t\t\t),\n\t\t)\n\t} else {\n\t\toptions = append(\n\t\t\toptions,\n\t\t\tgrpc.ChainUnaryInterceptor(\n\t\t\t\tunaryContextInterceptor,\n\t\t\t\tunaryErrorInterceptor,\n\t\t\t),\n\t\t\tgrpc.ChainStreamInterceptor(\n\t\t\t\tstreamContextInterceptor,\n\t\t\t\tstreamErrorInterceptor,\n\t\t\t),\n\t\t)\n\t}\n\treturn grpc.NewServer(options...)\n}\n\n// Serve accepts incoming connections on the listener lis, creating a new ServerTransport and service goroutine for each.\n// The service goroutines read gRPC requests and then call the registered handlers to reply to them. Serve returns when\n// lis.Accept fails with fatal errors.\n//\n// Serve waits until ctx.Done is closed. The svc.GracefulStop function will be called if the context has soft-cancel\n// enabled. The svc.Stop function will be called if no soft-cancel is enabled or when the GracefulStop doesn't finish\n// until the hard context is done.\nfunc Serve(ctx context.Context, svc *grpc.Server, lis net.Listener) error {\n\tclog.Debug(ctx, \"gRPC server started\")\n\tgo Wait(ctx, svc)\n\terr := svc.Serve(lis)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"gRPC server ended with error: %v\", err)\n\t} else {\n\t\tclog.Debug(ctx, \"gRPC server ended\")\n\t}\n\treturn err\n}\n\n// Stop the service by calling svc.Stop and give it maxTime to complete before returning.\n// If the maxTime expires and the current debug loglevel >= debug, then a stack trace of all goroutines will\n// be logged.\nfunc Stop(ctx context.Context, svc *grpc.Server, maxTime time.Duration) {\n\tdead := make(chan struct{})\n\tclog.Debug(ctx, \"Initiating shutdown\")\n\tgo func() {\n\t\tdefer close(dead)\n\t\tsvc.GracefulStop()\n\t\tclog.Debug(ctx, \"Shutdown complete\")\n\t}()\n\tselect {\n\tcase <-dead:\n\tcase <-time.After(maxTime):\n\t\t// Graceful shutdown is stuck! This shouldn't happen, and we need to find out why\n\t\tif clog.Enabled(ctx, slog.LevelDebug) {\n\t\t\tbuf := make([]byte, 1024*256)\n\t\t\tn := runtime.Stack(buf, true)\n\t\t\tclog.Debug(ctx, string(buf[:n]))\n\t\t}\n\t\tsvc.Stop()\n\t}\n}\n\n// Wait waits until the given contexts Done channel is closed. The server's GracefulStop function will be called\n// if the context has soft-cancel enabled. The server's Stop function will be called if no soft-cancel is enabled or\n// when the GracefulStop doesn't finish until the Done channel of the hard context closed.\nfunc Wait(ctx context.Context, svc *grpc.Server) {\n\t<-ctx.Done()\n\tStop(ctx, svc, 5*time.Second)\n}\n"
  },
  {
    "path": "pkg/grpc/watcher/watcher.go",
    "content": "package watcher\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n\n// WatchWithRetry establishes a server-streaming RPC connection and handles streamed data using the provided handler, with support for automatic retries on failure.\n//\n// The error returned will be nil when the retry operation ends because the context is canceled.\n//\n// Errors returned by the handler will be treated as permanent and returned as-is.\n//\n// Errors returned by the stream provider and the repair will be treated as transient and retried. The backup.Permanent wrapper can be used to override this behavior.\nfunc WatchWithRetry[T any](\n\tctx context.Context,\n\tname string,\n\tretryInterval time.Duration,\n\tstreamProvider func(context.Context) (grpc.ServerStreamingClient[T], error),\n\thandler func(*T) error,\n\trepair func() error,\n) error {\n\tretryCount := 0\n\terr := backoff.Retry(func() error {\n\t\tif retryCount > 0 && repair != nil {\n\t\t\tif err := repair(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tretryCount++\n\t\tstream, err := streamProvider(ctx)\n\t\tswitch status.Code(err) {\n\t\tcase codes.OK:\n\t\tcase codes.Unimplemented:\n\t\t\treturn backoff.Permanent(err)\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"error when calling stream provider for %s: %w\", name, err)\n\t\t}\n\t\tdefer func() {\n\t\t\t_ = stream.CloseSend()\n\t\t}()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn nil\n\t\t\tdefault:\n\t\t\t\tvalue, err := stream.Recv()\n\t\t\t\tswitch status.Code(err) {\n\t\t\t\tcase codes.OK:\n\t\t\t\t\terr = handler(value)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn backoff.Permanent(err)\n\t\t\t\t\t}\n\t\t\t\tcase codes.Unimplemented:\n\t\t\t\t\treturn backoff.Permanent(err)\n\t\t\t\tdefault:\n\t\t\t\t\treturn fmt.Errorf(\"error when calling Recv for %s: %w\", name, err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}, backoff.WithContext(backoff.NewConstantBackOff(retryInterval), ctx))\n\tif errors.Is(err, context.Canceled) {\n\t\terr = nil\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/icept/conflicts.go",
    "content": "package icept\n\nimport (\n\t\"regexp\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"k8s.io/api/core/v1\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/matcher\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// pathFiltersOverlap checks if two sets of path filters can match the same request path.\n// This properly handles the three path filter types: :path-equal:, :path-prefix:, and :path-regex:.\nfunc pathFiltersOverlap(paths1, paths2 []string) bool {\n\tfor _, path1 := range paths1 {\n\t\tfor _, path2 := range paths2 {\n\t\t\tif pathsCanMatch(path1, path2) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// pathsCanMatch determines if two path filters can match the same request path.\nfunc pathsCanMatch(filter1, filter2 string) bool {\n\treturn valuesCanMatch(matcher.PathValue(filter1), matcher.PathValue(filter2))\n}\n\n// valuesCanMatch determines if two matcher.Value objects could potentially match the same input.\n// It is used to detect conflicts between intercept path filters.\n// Supported operations: Equal, Prefix, Regex.\n//\n// The logic ensures that overlapping or equivalent patterns (e.g., /api/* vs /api/v1/*)\n// are treated as conflicts, while unrelated ones (e.g., /api/v1/* vs /api/v2/*) are not.\nfunc valuesCanMatch(v1, v2 matcher.Value) bool {\n\top1 := v1.Op()\n\top2 := v2.Op()\n\tpattern1 := v1.String()\n\tpattern2 := v2.String()\n\n\tswitch {\n\tcase op1 == matcher.ValueOpEqual && op2 == matcher.ValueOpEqual:\n\t\t// Both exact matches - conflict only if the same value\n\t\treturn pattern1 == pattern2\n\n\tcase op1 == matcher.ValueOpPrefix && op2 == matcher.ValueOpPrefix:\n\t\t// Both are prefixes - conflict if one is a prefix of the other\n\t\treturn strings.HasPrefix(pattern1, pattern2) || strings.HasPrefix(pattern2, pattern1)\n\n\tcase op1 == matcher.ValueOpPrefix && op2 == matcher.ValueOpEqual:\n\t\t// Prefix can match an exact value if the value starts with the prefix\n\t\treturn strings.HasPrefix(pattern2, pattern1)\n\n\tcase op1 == matcher.ValueOpEqual && op2 == matcher.ValueOpPrefix:\n\t\t// Exact value matches prefix if it starts with the prefix\n\t\treturn strings.HasPrefix(pattern1, pattern2)\n\n\tcase op1 == matcher.ValueOpRegex && op2 == matcher.ValueOpRegex:\n\t\t// --- Regex vs Regex ---\n\t\tif pattern1 == pattern2 {\n\t\t\treturn true\n\t\t}\n\n\t\tanchored1 := strings.HasPrefix(pattern1, \"^\")\n\t\tanchored2 := strings.HasPrefix(pattern2, \"^\")\n\n\t\tprefix1 := regexLiteralPrefix(pattern1)\n\t\tprefix2 := regexLiteralPrefix(pattern2)\n\n\t\t// Both anchored → only overlap if one is a prefix of the other\n\t\tif anchored1 && anchored2 {\n\t\t\treturn strings.HasPrefix(prefix1, prefix2) || strings.HasPrefix(prefix2, prefix1)\n\t\t}\n\t\t// Fallback conservative\n\t\treturn true\n\n\tcase op1 == matcher.ValueOpRegex && (op2 == matcher.ValueOpEqual || op2 == matcher.ValueOpPrefix):\n\t\treturn regexCanMatchValue(pattern1, op2, pattern2)\n\n\tcase op2 == matcher.ValueOpRegex && (op1 == matcher.ValueOpEqual || op1 == matcher.ValueOpPrefix):\n\t\treturn regexCanMatchValue(pattern2, op1, pattern1)\n\t}\n\n\t// Fallback: conservatively assume a potential match\n\treturn true\n}\n\n// regexCanMatchValue checks if a regex could match an equal or prefix value.\n// Conservative: returns true if regex is invalid or overlap can't be ruled out.\nfunc regexCanMatchValue(regexPattern string, op matcher.ValueOp, value string) bool {\n\tif value == \"\" {\n\t\treturn true\n\t}\n\n\tanchored := strings.HasPrefix(regexPattern, \"^\")\n\n\tswitch op {\n\tcase matcher.ValueOpEqual:\n\t\tif anchored {\n\t\t\tre, err := regexp.Compile(regexPattern)\n\t\t\tif err != nil {\n\t\t\t\treturn true // invalid regex → assume conflict\n\t\t\t}\n\t\t\treturn re.MatchString(value)\n\t\t}\n\t\t// Unanchored regex → can match anywhere in the string\n\t\treturn true\n\n\tcase matcher.ValueOpPrefix:\n\t\tif anchored {\n\t\t\t// Anchored regex → behaves like HasPrefix using literal prefix\n\t\t\tprefix := regexLiteralPrefix(regexPattern)\n\t\t\tif prefix == \"\" {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\treturn strings.HasPrefix(value, prefix) || strings.HasPrefix(prefix, value)\n\t\t}\n\n\t\t// Unanchored regex → can match anywhere in the string\n\t\treturn true\n\t}\n\t// fallback\n\treturn true\n}\n\n// regexLiteralPrefix safely extracts a literal prefix from a regex pattern.\n// Stops at first regex metacharacter. Conservative for complex regexes.\nfunc regexLiteralPrefix(pattern string) string {\n\tif pattern == \"\" {\n\t\treturn \"\"\n\t}\n\n\t// Remove the leading '^' (safe: TrimPrefix is a no-op if not present)\n\tpattern = strings.TrimPrefix(pattern, \"^\")\n\n\t// Remove trailing '.*' (safe: TrimSuffix is a no-op if not present)\n\tpattern = strings.TrimSuffix(pattern, \".*\")\n\n\tvar prefix strings.Builder\n\tfor _, r := range pattern {\n\t\t// Stop at any regex metacharacter\n\t\tif strings.ContainsRune(\"[](){}?+*|$.\\\\^\", r) {\n\t\t\tbreak\n\t\t}\n\t\tprefix.WriteRune(r)\n\t}\n\n\treturn prefix.String()\n}\n\n// ExplainConflict generates a human-readable explanation of why two intercept specs conflict.\nfunc ExplainConflict(spec1, spec2 *manager.InterceptSpec) string {\n\thasHeaders1 := len(spec1.HeaderFilters) > 0\n\thasHeaders2 := len(spec2.HeaderFilters) > 0\n\thasPaths1 := len(spec1.PathFilters) > 0\n\thasPaths2 := len(spec2.PathFilters) > 0\n\n\tisGlobal1 := !hasHeaders1 && !hasPaths1\n\tisGlobal2 := !hasHeaders2 && !hasPaths2\n\n\tif isGlobal1 || isGlobal2 {\n\t\treturn \"one intercept has no filters (intercepts all traffic)\"\n\t}\n\n\tif hasHeaders1 && hasHeaders2 {\n\t\t// Normalize headers for comparison\n\t\theaders1 := matcher.NewHeaders(spec1.HeaderFilters)\n\t\theaders2 := matcher.NewHeaders(spec2.HeaderFilters)\n\n\t\t// Check if headers form a subset relationship\n\t\tisSubset1of2 := isHeaderSubset(headers1, headers2)\n\t\tisSubset2of1 := isHeaderSubset(headers2, headers1)\n\n\t\tif isSubset1of2 || isSubset2of1 {\n\t\t\tif !hasPaths1 || !hasPaths2 || pathFiltersOverlap(spec1.PathFilters, spec2.PathFilters) {\n\t\t\t\tsubsetDesc := \"header filters\"\n\t\t\t\tif hasPaths1 && hasPaths2 {\n\t\t\t\t\tsubsetDesc += \" and paths\"\n\t\t\t\t}\n\t\t\t\treturn subsetDesc + \" overlap\"\n\t\t\t}\n\t\t}\n\t}\n\n\tif hasPaths1 && hasPaths2 && !hasHeaders1 && !hasHeaders2 {\n\t\treturn \"path filters overlap\"\n\t}\n\n\treturn \"filters would route the same traffic to different destinations\"\n}\n\n// isHeaderSubset checks if all headers in a subset exist in superset with matching values.\n// Uses wildcard matching for header values.\nfunc isHeaderSubset(subset, superset matcher.Headers) bool {\n\tsupersetMap := superset.HeaderMap()\n\tfor key, value := range subset.HeaderMap() {\n\t\tif superValue, exists := supersetMap[key]; !exists || !valuesCanMatch(value, superValue) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// IsInConflict determines if two intercept specs would conflict with each other. It's assumed\n// that `spec2` has been determined to be a potential conflict with `spec1` already with respect\n// to the agent and the targeted ports.\n//\n// Two specs conflict if they route the same traffic to different destinations.\n//\n// Precedence Model:\n// Headers take precedence over paths. This means intercepts with headers operate at a higher\n// priority tier than intercepts with only paths.\n//\n// Conflict Rules:\n//  1. Global intercepts (no headers, no paths) conflict with everything\n//  2. Headers vs. Paths: One spec with headers, another with only paths → NO CONFLICT\n//     (different priority tiers - headers are checked first, then paths)\n//  3. Both have headers: Conflict if headers form a subset AND paths overlap\n//     - Within each intercept, filters use AND logic (must match ALL headers AND ALL paths)\n//     - Example: {x-user:adam} vs. {x-user:adam, x-session:xyz} → CONFLICT (first is subset)\n//     - Example: {x-user:adam}+/api/* vs {x-user:adam}+/admin/* → NO CONFLICT (different paths)\n//  4. Both have only paths (no headers): Conflict if paths overlap\nfunc IsInConflict(spec1, spec2 *manager.InterceptSpec) bool {\n\thasHeaders1 := len(spec1.HeaderFilters) > 0\n\thasHeaders2 := len(spec2.HeaderFilters) > 0\n\thasPaths1 := len(spec1.PathFilters) > 0\n\thasPaths2 := len(spec2.PathFilters) > 0\n\n\t// Global intercept: no headers and no paths will intercept everything\n\tisGlobal1 := !hasHeaders1 && !hasPaths1\n\tisGlobal2 := !hasHeaders2 && !hasPaths2\n\n\t// Rule 2: Headers take precedence - different priority tiers don't conflict\n\t// If one spec has headers and the other has only paths, they operate at different tiers\n\tswitch {\n\tcase isGlobal1 || isGlobal2:\n\t\t// Rule 1: Global intercepts conflict with anything\n\t\treturn true\n\tcase hasHeaders1 && !hasHeaders2:\n\t\t// spec1 has headers (high priority), spec2 has only paths (low priority)\n\t\treturn false\n\tcase hasHeaders2 && !hasHeaders1:\n\t\t// spec2 has headers (high priority), spec1 has only paths (low priority)\n\t\treturn false\n\tcase hasHeaders1:\n\t\t// Rule 3: Both have headers - check for subset relationship AND path overlap\n\t\t// Normalize headers for case-insensitive comparison (HTTP headers per RFC 7230)\n\t\theaders1 := matcher.NewHeaders(spec1.HeaderFilters)\n\t\theaders2 := matcher.NewHeaders(spec2.HeaderFilters)\n\n\t\t// Check if headers form a subset relationship\n\t\thasHeaderSubset := isHeaderSubset(headers1, headers2) || isHeaderSubset(headers2, headers1)\n\t\tif !hasHeaderSubset {\n\t\t\t// Headers don't form a subset, no conflict\n\t\t\treturn false\n\t\t}\n\n\t\t// Headers form subset, now check paths\n\t\tif !hasPaths1 || !hasPaths2 {\n\t\t\t// At least one has no path restriction, so paths overlap\n\t\t\treturn true\n\t\t}\n\n\t\tfallthrough\n\tdefault:\n\t\t// Rule 4: Both have only paths (no headers) - check for path overlap\n\t\treturn pathFiltersOverlap(spec1.PathFilters, spec2.PathFilters)\n\t}\n}\n\n// PotentialConflict returns true if the given intercept spec may be in conflict with another intercept affecting the\n// given agent and ports.\nfunc PotentialConflict(agentName, namespace string, containerPorts []types.PortAndProto, info *manager.InterceptInfo) bool {\n\tswitch info.Disposition {\n\tcase manager.InterceptDispositionType_ACTIVE, manager.InterceptDispositionType_WAITING, manager.InterceptDispositionType_NO_AGENT:\n\t\tspec := info.Spec\n\t\tif !spec.Wiretap && spec.Agent == agentName && spec.Namespace == namespace {\n\t\t\tpp := types.PortAndProto{Port: uint16(spec.ContainerPort), Proto: types.FromK8sProtocol(v1.Protocol(spec.Protocol))}\n\t\t\tif slices.Contains(containerPorts, pp) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/icept/conflicts_test.go",
    "content": "package icept\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/matcher\"\n)\n\nfunc TestGenerateMechanismDescription(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tspec     *manager.InterceptSpec\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"TCP mechanism\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"tcp\",\n\t\t\t},\n\t\t\texpected: \"all TCP connections\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP mechanism with no filters\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t},\n\t\t\texpected: \"all TCP connections\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP mechanism with header filters only\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-User-ID\":     \"dev123\",\n\t\t\t\t\t\"X-Environment\": \"staging\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"HTTP requests with headers\\n 'X-Environment: staging'\\n 'X-User-Id: dev123'\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP mechanism with path filters only\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tPathFilters: []string{\n\t\t\t\t\t\":path-prefix:/api/v1/\",\n\t\t\t\t\t\":path-prefix:/admin/\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"HTTP requests with paths\\n prefix /api/v1/\\n prefix /admin/\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP mechanism with both header and path filters\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-User-ID\": \"dev123\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\n\t\t\t\t\t\":path-prefix:/api/\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"HTTP requests with path prefix /api/ and header 'X-User-Id: dev123'\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP mechanism with single header filter\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"Authorization\": \"Bearer token123\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"HTTP requests with header 'Authorization: Bearer token123'\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP mechanism with single path filter\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-equal:/health\"},\n\t\t\t},\n\t\t\texpected: \"HTTP requests with path == /health\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP mechanism with regex path filter\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/v[0-9]+/.*$\"},\n\t\t\t},\n\t\t\texpected: \"HTTP requests with path =~ ^/api/v[0-9]+/.*$\",\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP mechanism with mixed path filter types\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tPathFilters: []string{\n\t\t\t\t\t\":path-equal:/health\",\n\t\t\t\t\t\":path-prefix:/api/\",\n\t\t\t\t\t\":path-regex:^/admin/.*$\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"HTTP requests with paths\\n == /health\\n prefix /api/\\n =~ ^/admin/.*$\",\n\t\t},\n\t\t{\n\t\t\tname: \"unknown mechanism defaults to TCP\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"unknown\",\n\t\t\t},\n\t\t\texpected: \"all TCP connections\",\n\t\t},\n\t\t{\n\t\t\tname: \"empty mechanism defaults to TCP\",\n\t\t\tspec: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"\",\n\t\t\t},\n\t\t\texpected: \"all TCP connections\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := matcher.NewRequest(tt.spec.PathFilters, tt.spec.HeaderFilters).String()\n\t\t\tassert.Equal(t, tt.expected, result)\n\t\t})\n\t}\n}\n\nfunc TestInterceptSpecsConflict(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tspec1     *manager.InterceptSpec\n\t\tspec2     *manager.InterceptSpec\n\t\tconflicts bool\n\t}{\n\t\t{\n\t\t\tname: \"Issue #3969: Different header values for same key - should NOT conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"bertil\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Same header key and value - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Different header keys - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-session\": \"abc123\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"TCP intercepts (no filters) - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"tcp\",\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"tcp\",\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Different path filters - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\"/api/v1/*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\"/api/v2/*\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Same path filters - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Mixed filters: headers vs paths - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\"/admin/*\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Multiple headers with one overlap - should NOT conflict (not a subset)\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\":        \"adam\",\n\t\t\t\t\t\"x-environment\": \"dev\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\":    \"adam\",\n\t\t\t\t\t\"x-session\": \"xyz789\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Header subset - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\":    \"adam\",\n\t\t\t\t\t\"x-session\": \"xyz789\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Global intercept vs HTTP with filters - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"tcp\",\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Same headers, different paths - should NOT conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\"/admin/*\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Same headers, overlapping paths - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Subset headers with disjoint paths - should NOT conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\":    \"adam\",\n\t\t\t\t\t\"x-session\": \"xyz789\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\"/admin/*\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Subset headers with overlapping paths - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\":    \"adam\",\n\t\t\t\t\t\"x-session\": \"xyz789\",\n\t\t\t\t},\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP with only paths vs global - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\"/api/*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"tcp\",\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"HTTP with only headers, no paths - should conflict if headers are subset\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\":    \"adam\",\n\t\t\t\t\t\"x-session\": \"xyz789\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Case-insensitive header matching - X-User vs x-user should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-User\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Case-insensitive header matching with different values - should NOT conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-User\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"bertil\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Mixed case header variants - X-USER, x-User, X-user should all normalize to X-User\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"X-USER\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-User\": \"adam\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Wildcard header matching - x-user:dev-* should work properly\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"dev-*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"dev-*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Path prefix overlap - :path-prefix:/api/ vs :path-prefix:/api/v1/ should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/api/\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/api/v1/\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Path prefix no overlap - :path-prefix:/api/ vs :path-prefix:/admin/ should NOT conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/api/\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/admin/\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path equal vs prefix overlap - :path-equal:/api/users vs :path-prefix:/api/ should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-equal:/api/users\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/api/\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Path equal vs prefix no overlap - :path-equal:/admin/users vs :path-prefix:/api/ should NOT conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-equal:/admin/users\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/api/\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Path regex - :path-regex: should not conflict (distinct paths)\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/.*$\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/admin/\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Regex with same prefix - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/v1/.*\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Regex with same prefix - non-anchor- should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/api/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/api/v2/.*\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Regex with different prefix - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/admin/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/.*\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Exact with same value - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/health$\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-equal:/health\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Exact with different value - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/health$\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-equal:/ready\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Prefix with overlapping prefix - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/v1/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/api/\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Prefix with non-overlapping prefix - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/admin/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/api/\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Regex with exact equality pattern - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/status$\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/status$\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs Regex with different exact patterns - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/status$\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/health$\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - identical wildcard patterns should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - identical wildcard patterns should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"^adam::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - patterns with common suffix should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"^adam::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"bertil::adam::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - different prefixes in wildcard should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"^adam::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"^bertil::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - different prefixes in wildcard should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"adam::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"bertil::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - overlapping wildcard patterns should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-env\": \"stage::[a-z]+\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-env\": \"stage::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - one wildcard, one static exact value should conflict if regex includes it\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"prod::[a-z]+\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"prod::admin\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - non-overlapping static and wildcard should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"dev::[0-9]+\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"prod::[a-z]+\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - different header keys should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"dev::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-env\": \"stage::.*\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - one regex fully includes another (subset overlap)\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"dev::(admin|test)\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"dev::admin\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Header regex - disjoint sets in alternation should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"dev::(qa|test)\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism: \"http\",\n\t\t\t\tHeaderFilters: map[string]string{\n\t\t\t\t\t\"x-user\": \"dev::prod\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex literal prefix with escaped dot - should detect same literal prefix\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/v1\\\\.users/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/v1\\\\.users/details.*\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex literal prefix with escaped plus - should conflict due to same base literal\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/foo\\\\+bar/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/foo\\\\+bar/v1.*\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex with group starts early - should NOT conflict if literal diverges before '('\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/(v1|v2)/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/admin/(v1|v2)/.*\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex with nested group but same prefix - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/(v1|v2)/users.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/(v1|v2)/orders.*\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex with character class in middle - should conflict if literal prefix matches\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/user_[a-z]+/details\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/user_[0-9]+/settings\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex with non-overlapping prefixes - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/serviceA/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/serviceB/.*\"},\n\t\t\t},\n\t\t\tconflicts: false,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex with trailing literal only - should conflict if same literal\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/api/v1/resource$\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/api/v1/resource$\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex without ^ or .* but same base literal - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/foo/bar\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/foo/bar/v1\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex with escaped parentheses - same literal should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/file\\\\(test\\\\)/.*\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/file\\\\(test\\\\)/v1\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex with trailing $ anchor and identical literal - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:^/health$\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/health$\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs different Regex - non-anchored - should not conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/api\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/foo\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs different Prefix - non-anchored - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/api/\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-prefix:/foo/api/\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t\t{\n\t\t\tname: \"Regex vs overlapping Regex - non-anchored - should conflict\",\n\t\t\tspec1: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/api/\"},\n\t\t\t},\n\t\t\tspec2: &manager.InterceptSpec{\n\t\t\t\tMechanism:   \"http\",\n\t\t\t\tPathFilters: []string{\":path-regex:/foo/api/\"},\n\t\t\t},\n\t\t\tconflicts: true,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tresult := IsInConflict(tt.spec1, tt.spec2)\n\t\t\tassert.Equal(t, tt.conflicts, result)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/icept/find.go",
    "content": "package icept\n\nimport (\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// FindContainer finds the container configuration that matches the given InterceptSpec.\nfunc FindContainer(ac *agentconfig.Sidecar, spec *manager.InterceptSpec) (foundCN *agentconfig.Container, err error) {\n\tif spec.ContainerName == \"\" {\n\t\tif len(ac.Containers) == 1 {\n\t\t\treturn ac.Containers[0], nil\n\t\t}\n\t\treturn nil, errcat.User.Newf(\"%s %s.%s has more than one container\",\n\t\t\tac.WorkloadKind, ac.WorkloadName, ac.Namespace)\n\t}\n\tfor _, cn := range ac.Containers {\n\t\tif spec.ContainerName == cn.Name {\n\t\t\treturn cn, nil\n\t\t}\n\t}\n\treturn nil, errcat.User.Newf(\"%s %s.%s has no container named %s\",\n\t\tac.WorkloadKind, ac.WorkloadName, ac.Namespace, spec.ContainerName)\n}\n\n// FindIntercept finds the intercept configuration that matches either the given InterceptSpec's service/service port or a container port in case\n// the InterceptSpec targets a headless service.\nfunc FindIntercept(ac *agentconfig.Sidecar, spec *manager.InterceptSpec) (foundCN *agentconfig.Container, foundIC *agentconfig.Intercept, err error) {\n\treturn ac.FindIntercept(spec.ServiceName, spec.ContainerName, types.PortIdentifier(spec.PortIdentifier))\n}\n\n// FindContainerIntercept finds the intercept configuration that matches the given port identifier.\nfunc FindContainerIntercept(ac *agentconfig.Sidecar, cn *agentconfig.Container, pi types.PortIdentifier) (*agentconfig.Intercept, error) {\n\tfor _, ic := range cn.Intercepts {\n\t\tif agentconfig.IsInterceptForContainer(pi, ic) {\n\t\t\treturn ic, nil\n\t\t}\n\t}\n\treturn nil, errcat.User.Newf(\"%s %s.%s, container %s has no port matching %s\", ac.WorkloadKind, ac.WorkloadName, ac.Namespace, cn.Name, pi)\n}\n"
  },
  {
    "path": "pkg/informer/context.go",
    "content": "package informer\n\nimport (\n\t\"context\"\n\n\t\"github.com/puzpuzpuz/xsync/v4\"\n\t\"k8s.io/client-go/informers\"\n\n\targorolloutsinformer \"github.com/datawire/argo-rollouts-go-client/pkg/client/informers/externalversions\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\ntype factoryKey struct{}\n\nfunc getOpts(ns string) (k8sOpts []informers.SharedInformerOption, argoOpts []argorolloutsinformer.SharedInformerOption) {\n\tif ns != \"\" {\n\t\tk8sOpts = append(k8sOpts, informers.WithNamespace(ns))\n\t\targoOpts = append(argoOpts, argorolloutsinformer.WithNamespace(ns))\n\t}\n\n\treturn k8sOpts, argoOpts\n}\n\nfunc WithFactory(ctx context.Context, ns string) context.Context {\n\tif _, ok := ctx.Value(factoryKey{}).(*xsync.Map[string, GlobalFactory]); !ok {\n\t\tctx = context.WithValue(ctx, factoryKey{}, xsync.NewMap[string, GlobalFactory]())\n\t\tif ns == \"\" {\n\t\t\t// The cluster wide informer must be created when it is requested as the initial informer because it will act as a\n\t\t\t// proxy for all other requested informers.\n\t\t\tGetFactory(ctx, ns)\n\t\t}\n\t}\n\treturn ctx\n}\n\nfunc GetFactory(ctx context.Context, ns string) GlobalFactory {\n\tfm, ok := ctx.Value(factoryKey{}).(*xsync.Map[string, GlobalFactory])\n\tif !ok {\n\t\treturn nil\n\t}\n\tgf, _ := fm.LoadOrCompute(ns, func() (GlobalFactory, bool) {\n\t\tif ns != \"\" {\n\t\t\t// Return the cluster wide factory if one exists.\n\t\t\tif cw, ok := fm.Load(\"\"); ok {\n\t\t\t\treturn cw, false\n\t\t\t}\n\t\t}\n\t\tk8sOpts, argoOpts := getOpts(ns)\n\t\ti := k8sapi.GetJoinedClientSetInterface(ctx)\n\t\tk8sFactory := informers.NewSharedInformerFactoryWithOptions(i, 0, k8sOpts...)\n\t\targoRolloutFactory := argorolloutsinformer.NewSharedInformerFactoryWithOptions(i, 0, argoOpts...)\n\t\treturn NewDefaultGlobalFactory(k8sFactory, argoRolloutFactory), false\n\t})\n\treturn gf\n}\n\nfunc DropFactory(ctx context.Context, ns string) {\n\tif ns == \"\" {\n\t\treturn\n\t}\n\tif fm, ok := ctx.Value(factoryKey{}).(*xsync.Map[string, GlobalFactory]); ok {\n\t\tfm.Delete(ns)\n\t}\n}\n\nfunc GetK8sFactory(ctx context.Context, ns string) informers.SharedInformerFactory {\n\tf := GetFactory(ctx, ns)\n\tif f != nil {\n\t\treturn f.GetK8sInformerFactory()\n\t}\n\treturn nil\n}\n\nfunc GetArgoRolloutsFactory(ctx context.Context, ns string) argorolloutsinformer.SharedInformerFactory {\n\tf := GetFactory(ctx, ns)\n\tif f != nil {\n\t\treturn f.GetArgoRolloutsInformerFactory()\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/informer/factory.go",
    "content": "package informer\n\nimport (\n\t\"k8s.io/client-go/informers\"\n\n\targorolloutsinformer \"github.com/datawire/argo-rollouts-go-client/pkg/client/informers/externalversions\"\n)\n\ntype GlobalFactory interface {\n\tGetK8sInformerFactory() informers.SharedInformerFactory\n\tGetArgoRolloutsInformerFactory() argorolloutsinformer.SharedInformerFactory\n}\n\nfunc NewDefaultGlobalFactory(k8s informers.SharedInformerFactory, argoRollouts argorolloutsinformer.SharedInformerFactory) GlobalFactory {\n\treturn &defaultGlobalFactory{k8s: k8s, argoRollouts: argoRollouts}\n}\n\ntype defaultGlobalFactory struct {\n\tk8s          informers.SharedInformerFactory\n\targoRollouts argorolloutsinformer.SharedInformerFactory\n}\n\nfunc (f *defaultGlobalFactory) GetK8sInformerFactory() informers.SharedInformerFactory {\n\treturn f.k8s\n}\n\nfunc (f *defaultGlobalFactory) GetArgoRolloutsInformerFactory() argorolloutsinformer.SharedInformerFactory {\n\treturn f.argoRollouts\n}\n"
  },
  {
    "path": "pkg/ioutil/free_ports.go",
    "content": "package ioutil\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n)\n\n// FreePortsTCP uses net.Listen repeatedly to choose free TCP ports for the localhost. It then immediately closes\n// the listeners and returns the addresses that were allocated.\n//\n// NOTE: Since the listeners are closed, there's a chance that someone else might allocate the returned addresses\n// before they are actually used. The chances are slim, though, since tests show that in most cases (at least on\n// macOS and Linux), the same address isn't allocated for a while even if the allocation is made from different\n// processes.\nfunc FreePortsTCP(count int) ([]netip.AddrPort, error) {\n\tls := make([]net.Listener, 0, count)\n\tas := make([]netip.AddrPort, count)\n\tdefer func() {\n\t\tfor _, l := range ls {\n\t\t\t_ = l.Close()\n\t\t}\n\t}()\n\tfor i := 0; i < count; i++ {\n\t\tif l, err := net.Listen(\"tcp\", \":0\"); err != nil {\n\t\t\treturn nil, err\n\t\t} else {\n\t\t\tls = append(ls, l)\n\t\t\tas[i] = l.Addr().(*net.TCPAddr).AddrPort()\n\t\t}\n\t}\n\treturn as, nil\n}\n"
  },
  {
    "path": "pkg/ioutil/keyvalueformatter.go",
    "content": "package ioutil\n\nimport (\n\t\"io\"\n\t\"strings\"\n)\n\n// KeyValueFormatter will format each key/value pair added by Add so that they\n// are prefixed with Prefix and have a vertically aligned ':' between the key and\n// the value. Each pair is separated by a newline, and if a value contains newlines,\n// then each line in that value, except the first one will be prefixed with `Prefix`,\n// and Indent.\ntype KeyValueFormatter struct {\n\tkvs       []string\n\tPrefix    string\n\tIndent    string\n\tSeparator string\n}\n\ntype KeyValueProvider interface {\n\tAddTo(*KeyValueFormatter)\n}\n\nfunc DefaultKeyValueFormatter() *KeyValueFormatter {\n\treturn &KeyValueFormatter{\n\t\tIndent:    \"    \",\n\t\tSeparator: \": \",\n\t}\n}\n\n// Add adds a key value pair that will be included in the formatted output.\nfunc (f *KeyValueFormatter) Add(k, v string) {\n\tf.kvs = append(f.kvs, k, v)\n}\n\n// WriteTo writes the formatted output to the given io.Writer.\nfunc (f *KeyValueFormatter) WriteTo(out io.Writer) (int64, error) {\n\tkLen := 0\n\tkvs := f.kvs\n\tt := len(kvs)\n\n\t// Figure out length of the longest key\n\tfor i := 0; i < t; i += 2 {\n\t\tif l := len(kvs[i]); l > kLen {\n\t\t\tkLen = l\n\t\t}\n\t}\n\tn := 0\n\tfor i := 0; i < t; i += 2 {\n\t\tif i > 0 {\n\t\t\tn += WriteString(out, \"\\n\")\n\t\t}\n\t\tlines := strings.Split(strings.TrimRight(kvs[i+1], \" \\t\\r\\n\"), \"\\n\")\n\t\tn += Printf(out, \"%s%-*s%s%s\", f.Prefix, kLen, kvs[i], f.Separator, lines[0])\n\t\tfor _, line := range lines[1:] {\n\t\t\tn += Printf(out, \"\\n%s%s%s\", f.Prefix, f.Indent, line)\n\t\t}\n\t}\n\treturn int64(n), nil\n}\n\nfunc (f *KeyValueFormatter) Println(out io.Writer) int {\n\tn, _ := f.WriteTo(out)\n\treturn int(n) + Println(out, \"\")\n}\n\n// String returns the formatted output string.\nfunc (f *KeyValueFormatter) String() string {\n\tsb := &strings.Builder{}\n\t_, _ = f.WriteTo(sb)\n\treturn sb.String()\n}\n"
  },
  {
    "path": "pkg/ioutil/print.go",
    "content": "package ioutil\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n)\n\n// Print is like Fprint but panics on error.\nfunc Print(out io.Writer, args ...any) int {\n\tn, err := fmt.Fprint(out, args...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\n// Println is like Fprintln but panics on error.\nfunc Println(out io.Writer, args ...any) int {\n\tn, err := fmt.Fprintln(out, args...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\n// Printf is like Fprintf but panics on error.\nfunc Printf(out io.Writer, format string, args ...any) int {\n\tn, err := fmt.Fprintf(out, format, args...)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\n// WriteString is like io.WriteString but panics on error.\nfunc WriteString(out io.Writer, s string) int {\n\tn, err := io.WriteString(out, s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc WriterToString(wt func(w io.Writer) (int64, error)) string {\n\tvar sb strings.Builder\n\t_, err := wt(&sb)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn sb.String()\n}\n"
  },
  {
    "path": "pkg/ioutil/safename.go",
    "content": "package ioutil\n\nimport \"strings\"\n\n// SafeName returns a string that can safely be used as a file name or docker container. Only\n// characters [a-zA-Z0-9][a-zA-Z0-9_.-] are allowed. Others are replaced by an underscore, or\n// if it's the very first character, by the character 'a'.\nfunc SafeName(name string) string {\n\tn := strings.Builder{}\n\tfor i, c := range name {\n\t\tswitch {\n\t\tcase (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'):\n\t\t\tn.WriteByte(byte(c))\n\t\tcase i > 0 && (c == '_' || c == '.' || c == '-'):\n\t\t\tn.WriteByte(byte(c))\n\t\tcase i > 0:\n\t\t\tn.WriteByte('_')\n\t\tdefault:\n\t\t\tn.WriteByte('a')\n\t\t}\n\t}\n\treturn n.String()\n}\n"
  },
  {
    "path": "pkg/ioutil/tempname.go",
    "content": "package ioutil\n\nimport \"os\"\n\n// CreateTempName creates a new temporary file using os.CreateTemp, removes it, and then returns its name.\nfunc CreateTempName(dir, pattern string) (string, error) {\n\tf, err := os.CreateTemp(dir, pattern)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tpath := f.Name()\n\t_ = f.Close()\n\t_ = os.Remove(path)\n\treturn path, nil\n}\n"
  },
  {
    "path": "pkg/ioutil/writeallto.go",
    "content": "package ioutil\n\nimport \"io\"\n\n// WriterTos implements something that can be represented as a list of io.WriterTo.\ntype WriterTos interface {\n\tWriterTos() []io.WriterTo\n}\n\n// WriteAllTo calls WriteTo on all elements of the WriterTo slice and\n// returns the total number of bytes written.\nfunc WriteAllTo(out io.Writer, wts ...io.WriterTo) (tn int64, err error) {\n\tfor _, wt := range wts {\n\t\tif wt == nil {\n\t\t\tcontinue\n\t\t}\n\t\tvar n int64\n\t\tif n, err = wt.WriteTo(out); err != nil {\n\t\t\treturn tn, err\n\t\t}\n\t\ttn += n\n\t}\n\treturn tn, nil\n}\n"
  },
  {
    "path": "pkg/iputil/ipnet.go",
    "content": "package iputil\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n)\n\nfunc PrefixToRPC(n netip.Prefix) *manager.IPNet {\n\treturn &manager.IPNet{\n\t\tIp:   n.Addr().AsSlice(),\n\t\tMask: int32(n.Bits()),\n\t}\n}\n\nfunc PrefixesToRPC(n []netip.Prefix) []*manager.IPNet {\n\tl := len(n)\n\tif l == 0 {\n\t\treturn nil\n\t}\n\tss := make([]*manager.IPNet, l)\n\tfor i, m := range n {\n\t\tss[i] = PrefixToRPC(m)\n\t}\n\treturn ss\n}\n\nfunc RPCToPrefix(m *manager.IPNet) netip.Prefix {\n\tif a, ok := netip.AddrFromSlice(m.Ip); ok {\n\t\treturn netip.PrefixFrom(a, int(m.Mask))\n\t}\n\treturn netip.Prefix{}\n}\n\nfunc RPCsToPrefixes(n []*manager.IPNet) []netip.Prefix {\n\tl := len(n)\n\tif l == 0 {\n\t\treturn nil\n\t}\n\tss := make([]netip.Prefix, l)\n\tfor i, m := range n {\n\t\tss[i] = RPCToPrefix(m)\n\t}\n\treturn ss\n}\n\nfunc PrefixFromIPNet(ipNet *net.IPNet) (pfx netip.Prefix) {\n\tif ipNet == nil {\n\t\treturn pfx\n\t}\n\tif addr, ok := netip.AddrFromSlice(ipNet.IP); ok {\n\t\tif addr.Is4In6() {\n\t\t\taddr = netip.AddrFrom4(addr.As4())\n\t\t}\n\t\tones, _ := ipNet.Mask.Size()\n\t\tpfx = netip.PrefixFrom(addr, ones)\n\t}\n\treturn pfx\n}\n"
  },
  {
    "path": "pkg/iputil/ips.go",
    "content": "package iputil\n\nimport (\n\t\"bytes\"\n\t\"net\"\n\t\"sort\"\n\t\"strings\"\n)\n\ntype IPs []net.IP\n\nfunc IPsFromBytesSlice(bss [][]byte) IPs {\n\tips := make(IPs, len(bss))\n\tfor i, bs := range bss {\n\t\tips[i] = bs\n\t}\n\treturn ips\n}\n\nfunc (ips IPs) String() string {\n\tnips := len(ips)\n\tswitch nips {\n\tcase 0:\n\t\treturn \"\"\n\tcase 1:\n\t\treturn ips[0].String()\n\tdefault:\n\t\tsb := strings.Builder{}\n\t\tsb.WriteString(ips[0].String())\n\t\tfor i := 1; i < nips; i++ {\n\t\t\tsb.WriteByte(',')\n\t\t\tsb.WriteString(ips[i].String())\n\t\t}\n\t\treturn sb.String()\n\t}\n}\n\nfunc (ips IPs) UniqueSorted() IPs {\n\treturn UniqueSorted(ips)\n}\n\nfunc UniqueSorted(ips []net.IP) IPs {\n\tfor i, ip := range ips {\n\t\tif ip4 := ip.To4(); ip4 != nil {\n\t\t\tips[i] = ip4\n\t\t}\n\t}\n\tsort.Slice(ips, func(i, j int) bool {\n\t\treturn bytes.Compare(ips[i], ips[j]) < 0\n\t})\n\tvar prev net.IP\n\tlast := len(ips) - 1\n\tfor i := 0; i <= last; i++ {\n\t\ts := ips[i]\n\t\tif s.Equal(prev) {\n\t\t\tcopy(ips[i:], ips[i+1:])\n\t\t\tlast--\n\t\t\ti--\n\t\t} else {\n\t\t\tprev = s\n\t\t}\n\t}\n\treturn ips[:last+1]\n}\n\n// BytesSlice is returns a [][]byte copy of the IPs.\nfunc (ips IPs) BytesSlice() [][]byte {\n\tbss := make([][]byte, len(ips))\n\tfor i, bs := range ips {\n\t\tbss[i] = bs\n\t}\n\treturn bss\n}\n"
  },
  {
    "path": "pkg/iputil/join.go",
    "content": "package iputil\n\nimport (\n\t\"net\"\n\t\"strconv\"\n)\n\nfunc JoinIpPort(ip net.IP, port uint16) string {\n\tps := strconv.Itoa(int(port))\n\tif ip4 := ip.To4(); ip4 != nil {\n\t\treturn ip4.String() + \":\" + ps\n\t}\n\tif ip16 := ip.To16(); ip16 != nil {\n\t\treturn \"[\" + ip16.String() + \"]:\" + ps\n\t}\n\treturn \":\" + ps\n}\n\nfunc JoinHostPort(host string, port uint16) string {\n\treturn net.JoinHostPort(host, strconv.Itoa(int(port)))\n}\n"
  },
  {
    "path": "pkg/iputil/normalize.go",
    "content": "package iputil\n\nimport (\n\t\"net\"\n\t\"strings\"\n)\n\nfunc IsIpV6Addr(ipAddStr string) bool {\n\treturn strings.Count(ipAddStr, \":\") >= 2\n}\n\n// Normalize returns the four byte version of an IPv4, even if it\n// was expressed as a 16 byte IP.\nfunc Normalize(ip net.IP) net.IP {\n\tif ip4 := ip.To4(); ip4 != nil {\n\t\tip = ip4\n\t}\n\treturn ip\n}\n"
  },
  {
    "path": "pkg/iputil/parse.go",
    "content": "package iputil\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n)\n\n// ParseAddr is like netip.ParseAddr but removes any IPv4-mapped IPv6 address prefix.\nfunc ParseAddr(ipStr string) (ip netip.Addr, err error) {\n\tif ip, err = netip.ParseAddr(ipStr); err == nil {\n\t\tip = ip.Unmap()\n\t}\n\treturn ip, err\n}\n\n// SplitToIPPort splits the given address into an IP and a port number. It's\n// an error if the address is based on a hostname rather than an IP.\nfunc SplitToIPPort(netAddr net.Addr) (netip.AddrPort, error) {\n\tipAddr, ok := netAddr.(interface{ AddrPort() netip.AddrPort })\n\tif !ok {\n\t\treturn netip.AddrPort{}, fmt.Errorf(\"address %q is not an IP:port address\", netAddr)\n\t}\n\treturn ipAddr.AddrPort(), nil\n}\n"
  },
  {
    "path": "pkg/json/marshal.go",
    "content": "package json\n\nimport (\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\n// Marshal serializes the given value to JSON deterministically and enforces unit serialization of durations.\nfunc Marshal(value any) ([]byte, error) {\n\topts := json.WithMarshalers(json.MarshalFunc(func(d time.Duration) ([]byte, error) {\n\t\treturn json.Marshal(d.String())\n\t}))\n\treturn json.Marshal(value, opts, json.Deterministic(true))\n}\n\n// Unmarshal deserializes the given JSON data into the given value. Durations are deserialized using the\n// time.ParseDuration function (unit-aware).\nfunc Unmarshal(data []byte, into any, rejectUnknown bool) error {\n\topts := []json.Options{json.WithUnmarshalers(\n\t\tjson.JoinUnmarshalers(\n\t\t\tjson.UnmarshalFunc(func(b []byte, v *time.Duration) error {\n\t\t\t\tvar s string\n\t\t\t\terr := json.Unmarshal(b, &s)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\td, err := time.ParseDuration(s)\n\t\t\t\tif err == nil {\n\t\t\t\t\t*v = d\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}),\n\t\t\tjson.UnmarshalFunc(func(b []byte, v *slog.Level) error {\n\t\t\t\tvar s string\n\t\t\t\terr := json.Unmarshal(b, &s)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\td, err := clog.ParseLevel(s)\n\t\t\t\tif err == nil {\n\t\t\t\t\t*v = d\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}),\n\t\t))}\n\tif rejectUnknown {\n\t\topts = append(opts, json.RejectUnknownMembers(true))\n\t}\n\treturn json.Unmarshal(data, into, opts...)\n}\n"
  },
  {
    "path": "pkg/k8sapi/cani.go",
    "content": "package k8sapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\tauth \"k8s.io/api/authorization/v1\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nfunc CanI(ctx context.Context, ras ...*auth.ResourceAttributes) (bool, error) {\n\tauthHandler := GetK8sInterface(ctx).AuthorizationV1().SelfSubjectAccessReviews()\n\tfor _, ra := range ras {\n\t\treview := auth.SelfSubjectAccessReview{Spec: auth.SelfSubjectAccessReviewSpec{ResourceAttributes: ra}}\n\t\tar, err := authHandler.Create(ctx, &review, meta.CreateOptions{})\n\t\tif err == nil && ar.Status.Allowed {\n\t\t\tcontinue\n\t\t}\n\t\twhere := \"\"\n\t\tif ra.Namespace != \"\" {\n\t\t\twhere = \" in namespace \" + ra.Namespace\n\t\t}\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(`unable to do \"can-i %s %s%s\": %v`, ra.Verb, ra.Resource, where, err)\n\t\t\tif ctx.Err() == nil {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\t} else {\n\t\t\tclog.Infof(ctx, `\"can-i %s %s%s\" is not allowed`, ra.Verb, ra.Resource, where)\n\t\t}\n\t\treturn false, err\n\t}\n\treturn true, nil\n}\n\nfunc CanWatch(ctx context.Context, group, resource, name, ns string) bool {\n\tif name == \"\" {\n\t\tok, err := CanI(ctx, &auth.ResourceAttributes{\n\t\t\tVerb:      \"list\",\n\t\t\tResource:  resource,\n\t\t\tGroup:     group,\n\t\t\tNamespace: ns,\n\t\t})\n\t\tif err != nil || !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\tok, err := CanI(ctx, &auth.ResourceAttributes{\n\t\tVerb:      \"watch\",\n\t\tResource:  resource,\n\t\tName:      name,\n\t\tGroup:     group,\n\t\tNamespace: ns,\n\t})\n\tif err != nil || !ok {\n\t\treturn false\n\t}\n\treturn true\n}\n\n// CanWatchNamespaces answers the question if this client has the RBAC permissions necessary\n// to watch namespaces. The answer is likely false when using a namespaces scoped installation.\nfunc CanWatchNamespaces(ctx context.Context) bool {\n\treturn CanWatch(ctx, \"\", \"namespaces\", \"\", \"\")\n}\n"
  },
  {
    "path": "pkg/k8sapi/clientconfigprovider.go",
    "content": "package k8sapi\n\nimport \"k8s.io/client-go/tools/clientcmd\"\n\ntype ClientConfigProvider interface {\n\tClientConfig() (clientcmd.ClientConfig, error)\n}\n"
  },
  {
    "path": "pkg/k8sapi/interface.go",
    "content": "package k8sapi\n\nimport (\n\t\"k8s.io/client-go/kubernetes\"\n\n\targoRollouts \"github.com/datawire/argo-rollouts-go-client/pkg/client/clientset/versioned\"\n\ttypedArgoRollouts \"github.com/datawire/argo-rollouts-go-client/pkg/client/clientset/versioned/typed/rollouts/v1alpha1\"\n)\n\ntype JoinedClientSetInterface interface {\n\tkubernetes.Interface\n\targoRollouts.Interface\n}\n\ntype joinedClientSetInterface struct {\n\tkubernetes.Interface\n\tari argoRollouts.Interface\n}\n\nfunc (j joinedClientSetInterface) ArgoprojV1alpha1() typedArgoRollouts.ArgoprojV1alpha1Interface {\n\treturn j.ari.ArgoprojV1alpha1()\n}\n"
  },
  {
    "path": "pkg/k8sapi/kind.go",
    "content": "package k8sapi\n\nimport \"slices\"\n\ntype Kind string\n\nconst (\n\tServiceKind     Kind = \"Service\"\n\tPodKind         Kind = \"Pod\"\n\tDeploymentKind  Kind = \"Deployment\"\n\tStatefulSetKind Kind = \"StatefulSet\"\n\tReplicaSetKind  Kind = \"ReplicaSet\"\n\tRolloutKind     Kind = \"Rollout\"\n)\n\ntype Kinds []Kind\n\nfunc (k Kinds) Contains(kind Kind) bool {\n\treturn slices.Contains(k, kind)\n}\n\nvar (\n\tKnownKinds         = Kinds{ServiceKind, PodKind, DeploymentKind, StatefulSetKind, ReplicaSetKind, RolloutKind} //nolint:gochecknoglobals // constant\n\tKnownWorkloadKinds = Kinds{DeploymentKind, ReplicaSetKind, StatefulSetKind, RolloutKind}                       //nolint:gochecknoglobals // constant\n)\n\nfunc (w Kind) IsValid() bool {\n\treturn KnownKinds.Contains(w)\n}\n"
  },
  {
    "path": "pkg/k8sapi/namespaceid.go",
    "content": "package k8sapi\n\nimport (\n\t\"context\"\n\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// GetNamespaceID returns the uuid for a given namespace.  If there is an error, it still\n// returns a usable ID along with the error.\nfunc GetNamespaceID(ctx context.Context, namespace string) (clusterID string, err error) {\n\tns, err := GetK8sInterface(ctx).CoreV1().Namespaces().Get(ctx, namespace, v1.GetOptions{})\n\tif err != nil {\n\t\t// But still return a usable ID if there's an error.\n\t\treturn \"00000000-0000-0000-0000-000000000000\", err\n\t}\n\treturn string(ns.GetUID()), nil\n}\n"
  },
  {
    "path": "pkg/k8sapi/object.go",
    "content": "package k8sapi\n\nimport (\n\t\"context\"\n\n\tcore \"k8s.io/api/core/v1\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\ttypedCore \"k8s.io/client-go/kubernetes/typed/core/v1\"\n)\n\ntype Object interface {\n\truntime.Object\n\tmeta.Object\n\tGetAnnotations() map[string]string\n\tGetKind() Kind\n\tDelete(context.Context) error\n\tRefresh(context.Context) error\n\tSelector() (labels.Selector, error)\n\tUpdate(context.Context) error\n\tPatch(context.Context, types.PatchType, []byte, ...string) error\n\tGetGroupResource() schema.GroupResource\n}\n\nfunc GetService(c context.Context, name, namespace string) (Object, error) {\n\td, err := services(c, namespace).Get(c, name, meta.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &service{d}, nil\n}\n\n// Services returns all services found in the given Namespace.\nfunc Services(c context.Context, namespace string, labelSelector labels.Set) ([]Object, error) {\n\tls, err := services(c, namespace).List(c, listOptions(labelSelector))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tis := ls.Items\n\tos := make([]Object, len(is))\n\tfor i := range is {\n\t\tos[i] = Service(&is[i])\n\t}\n\treturn os, nil\n}\n\nfunc Service(d *core.Service) Object {\n\treturn &service{d}\n}\n\n// ServiceImpl casts the given Object as an *core.Service and returns\n// it together with a status flag indicating whether the cast was possible.\nfunc ServiceImpl(o Object) (*core.Service, bool) {\n\tif s, ok := o.(*service); ok {\n\t\treturn s.Service, true\n\t}\n\treturn nil, false\n}\n\nfunc GetPod(c context.Context, name, namespace string) (Object, error) {\n\td, err := pods(c, namespace).Get(c, name, meta.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &pod{d}, nil\n}\n\n// Pods returns all pods found in the given Namespace.\nfunc Pods(c context.Context, namespace string, labelSelector labels.Set) ([]Object, error) {\n\tls, err := pods(c, namespace).List(c, listOptions(labelSelector))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tis := ls.Items\n\tos := make([]Object, len(is))\n\tfor i := range is {\n\t\tos[i] = Pod(&is[i])\n\t}\n\treturn os, nil\n}\n\nfunc Pod(d *core.Pod) Object {\n\treturn &pod{d}\n}\n\n// PodImpl casts the given Object as an *core.Pod and returns\n// it together with a status flag indicating whether the cast was possible.\nfunc PodImpl(o Object) (*core.Pod, bool) {\n\tif s, ok := o.(*pod); ok {\n\t\treturn s.Pod, true\n\t}\n\treturn nil, false\n}\n\ntype service struct {\n\t*core.Service\n}\n\nfunc services(c context.Context, namespace string) typedCore.ServiceInterface {\n\treturn GetK8sInterface(c).CoreV1().Services(namespace)\n}\n\nfunc (o *service) ki(c context.Context) typedCore.ServiceInterface {\n\treturn services(c, o.Namespace)\n}\n\nfunc (o *service) GetGroupResource() schema.GroupResource {\n\treturn schema.GroupResource{\n\t\tGroup:    o.TypeMeta.GroupVersionKind().Group,\n\t\tResource: \"services\",\n\t}\n}\n\nfunc (o *service) GetKind() Kind {\n\treturn ServiceKind\n}\n\nfunc (o *service) Delete(c context.Context) error {\n\treturn o.ki(c).Delete(c, o.Name, meta.DeleteOptions{})\n}\n\nfunc (o *service) Patch(c context.Context, pt types.PatchType, data []byte, subresources ...string) error {\n\td, err := o.ki(c).Patch(c, o.Name, pt, data, meta.PatchOptions{}, subresources...)\n\tif err == nil {\n\t\to.Service = d\n\t}\n\treturn err\n}\n\nfunc (o *service) Refresh(c context.Context) error {\n\td, err := o.ki(c).Get(c, o.Name, meta.GetOptions{})\n\tif err == nil {\n\t\to.Service = d\n\t}\n\treturn err\n}\n\nfunc (o *service) Selector() (labels.Selector, error) {\n\tif len(o.Spec.Selector) == 0 {\n\t\treturn nil, nil\n\t}\n\treturn labels.SelectorFromSet(o.Spec.Selector), nil\n}\n\nfunc (o *service) String() string {\n\treturn String(o)\n}\n\nfunc (o *service) Update(c context.Context) error {\n\td, err := o.ki(c).Update(c, o.Service, meta.UpdateOptions{})\n\tif err == nil {\n\t\to.Service = d\n\t}\n\treturn err\n}\n\ntype pod struct {\n\t*core.Pod\n}\n\nfunc pods(c context.Context, namespace string) typedCore.PodInterface {\n\treturn GetK8sInterface(c).CoreV1().Pods(namespace)\n}\n\nfunc (o *pod) ki(c context.Context) typedCore.PodInterface {\n\treturn pods(c, o.Namespace)\n}\n\nfunc (o *pod) GetGroupResource() schema.GroupResource {\n\treturn schema.GroupResource{\n\t\tGroup:    o.TypeMeta.GroupVersionKind().Group,\n\t\tResource: \"pods\",\n\t}\n}\n\nfunc (o *pod) GetKind() Kind {\n\treturn PodKind\n}\n\nfunc (o *pod) Delete(c context.Context) error {\n\treturn o.ki(c).Delete(c, o.Name, meta.DeleteOptions{})\n}\n\nfunc (o *pod) Patch(c context.Context, pt types.PatchType, data []byte, subresources ...string) error {\n\td, err := o.ki(c).Patch(c, o.Name, pt, data, meta.PatchOptions{}, subresources...)\n\tif err == nil {\n\t\to.Pod = d\n\t}\n\treturn err\n}\n\nfunc (o *pod) Refresh(c context.Context) error {\n\td, err := o.ki(c).Get(c, o.Name, meta.GetOptions{})\n\tif err == nil {\n\t\to.Pod = d\n\t}\n\treturn err\n}\n\nfunc (o *pod) Selector() (labels.Selector, error) {\n\treturn nil, nil\n}\n\nfunc (o *pod) String() string {\n\treturn String(o)\n}\n\nfunc (o *pod) Update(c context.Context) error {\n\td, err := o.ki(c).Update(c, o.Pod, meta.UpdateOptions{})\n\tif err == nil {\n\t\to.Pod = d\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/k8sapi/util.go",
    "content": "package k8sapi\n\nimport (\n\t\"context\"\n\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/kubernetes\"\n\n\targoRollouts \"github.com/datawire/argo-rollouts-go-client/pkg/client/clientset/versioned\"\n)\n\nfunc WithJoinedClientSetInterface(ctx context.Context, ki kubernetes.Interface, ari argoRollouts.Interface) context.Context {\n\treturn WithArgoRolloutsInterface(WithK8sInterface(ctx, ki), ari)\n}\n\nfunc GetJoinedClientSetInterface(ctx context.Context) JoinedClientSetInterface {\n\treturn &joinedClientSetInterface{\n\t\tGetK8sInterface(ctx),\n\t\tGetArgoRolloutsInterface(ctx),\n\t}\n}\n\nfunc WithArgoRolloutsInterface(ctx context.Context, ari argoRollouts.Interface) context.Context {\n\treturn context.WithValue(ctx, ariKey{}, ari)\n}\n\nfunc WithK8sInterface(ctx context.Context, ki kubernetes.Interface) context.Context {\n\treturn context.WithValue(ctx, kiKey{}, ki)\n}\n\nfunc GetArgoRolloutsInterface(ctx context.Context) argoRollouts.Interface {\n\tari, ok := ctx.Value(ariKey{}).(argoRollouts.Interface)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn ari\n}\n\nfunc GetK8sInterface(ctx context.Context) kubernetes.Interface {\n\tki, ok := ctx.Value(kiKey{}).(kubernetes.Interface)\n\tif !ok {\n\t\tpanic(\"K8sInterface requested from a context that has none\")\n\t}\n\treturn ki\n}\n\ntype kiKey struct{}\n\ntype ariKey struct{}\n\nfunc listOptions(labelSelector labels.Set) meta.ListOptions {\n\topts := meta.ListOptions{}\n\tif len(labelSelector) > 0 {\n\t\topts.LabelSelector = labels.SelectorFromSet(labelSelector).String()\n\t}\n\treturn opts\n}\n"
  },
  {
    "path": "pkg/k8sapi/workload.go",
    "content": "package k8sapi\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strconv\"\n\n\tapps \"k8s.io/api/apps/v1\"\n\tcore \"k8s.io/api/core/v1\"\n\tk8sErrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\ttypedApps \"k8s.io/client-go/kubernetes/typed/apps/v1\"\n\n\targoRollouts \"github.com/datawire/argo-rollouts-go-client/pkg/apis/rollouts/v1alpha1\"\n\ttypedArgoRollouts \"github.com/datawire/argo-rollouts-go-client/pkg/client/clientset/versioned/typed/rollouts/v1alpha1\"\n)\n\ntype Workload interface {\n\tObject\n\tGetPodTemplate() *core.PodTemplateSpec\n\tReplicas() int\n\tUpdated(int64) bool\n}\n\ntype UnsupportedWorkloadKindError Kind\n\nfunc (u UnsupportedWorkloadKindError) Error() string {\n\treturn fmt.Sprintf(\"unsupported workload kind: %q\", string(u))\n}\n\n// GetWorkload returns a workload for the given name, namespace, and workloadKind. The workloadKind\n// is optional. A search is performed in the following order if it is empty:\n//\n//  1. Deployments\n//  2. ReplicaSets\n//  3. StatefulSets\n//  4. Rollouts (Argo Rollouts)\n//\n// The first match is returned.\nfunc GetWorkload(c context.Context, name, namespace string, kind Kind) (obj Workload, err error) {\n\tswitch kind {\n\tcase DeploymentKind:\n\t\tobj, err = GetDeployment(c, name, namespace)\n\tcase ReplicaSetKind:\n\t\tobj, err = GetReplicaSet(c, name, namespace)\n\tcase StatefulSetKind:\n\t\tobj, err = GetStatefulSet(c, name, namespace)\n\tcase RolloutKind:\n\t\tobj, err = GetRollout(c, name, namespace)\n\tcase \"\":\n\t\tfor _, wk := range KnownWorkloadKinds {\n\t\t\tif obj, err = GetWorkload(c, name, namespace, wk); err == nil {\n\t\t\t\treturn obj, nil\n\t\t\t}\n\t\t\tif !k8sErrors.IsNotFound(err) {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\terr = k8sErrors.NewNotFound(core.Resource(\"workload\"), name+\".\"+namespace)\n\tdefault:\n\t\treturn nil, UnsupportedWorkloadKindError(kind)\n\t}\n\treturn obj, err\n}\n\nfunc WrapWorkload(workload runtime.Object) (Workload, error) {\n\tswitch workload := workload.(type) {\n\tcase *apps.Deployment:\n\t\treturn Deployment(workload), nil\n\tcase *apps.ReplicaSet:\n\t\treturn ReplicaSet(workload), nil\n\tcase *apps.StatefulSet:\n\t\treturn StatefulSet(workload), nil\n\tcase *argoRollouts.Rollout:\n\t\treturn Rollout(workload), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported workload type %T\", workload)\n\t}\n}\n\nfunc GetDeployment(c context.Context, name, namespace string) (Workload, error) {\n\td, err := deployments(c, namespace).Get(c, name, meta.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &deployment{d}, nil\n}\n\n// Deployments returns all deployments found in the given Namespace.\nfunc Deployments(c context.Context, namespace string, labelSelector labels.Set) ([]Workload, error) {\n\tls, err := deployments(c, namespace).List(c, listOptions(labelSelector))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tis := ls.Items\n\tos := make([]Workload, len(is))\n\tfor i := range is {\n\t\tos[i] = Deployment(&is[i])\n\t}\n\treturn os, nil\n}\n\nfunc Deployment(d *apps.Deployment) Workload {\n\treturn &deployment{d}\n}\n\n// DeploymentImpl casts the given Object as an *apps.Deployment and returns\n// it together with a status flag indicating whether the cast was possible.\nfunc DeploymentImpl(o Object) (*apps.Deployment, bool) {\n\tif s, ok := o.(*deployment); ok {\n\t\treturn s.Deployment, true\n\t}\n\treturn nil, false\n}\n\nfunc GetRollout(c context.Context, name, namespace string) (Workload, error) {\n\tclient := rollouts(c, namespace)\n\tif client == nil {\n\t\treturn nil, fmt.Errorf(\"argo rollouts client not available\")\n\t}\n\n\tr, err := client.Get(c, name, meta.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &rollout{r}, nil\n}\n\n// Rollouts returns all rollouts found in the given Namespace.\nfunc Rollouts(c context.Context, namespace string, labelSelector labels.Set) ([]Workload, error) {\n\tclient := rollouts(c, namespace)\n\tif client == nil {\n\t\treturn nil, fmt.Errorf(\"argo rollouts client not available\")\n\t}\n\n\tls, err := client.List(c, listOptions(labelSelector))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tis := ls.Items\n\tos := make([]Workload, len(is))\n\tfor i := range is {\n\t\tos[i] = Rollout(&is[i])\n\t}\n\treturn os, nil\n}\n\nfunc Rollout(r *argoRollouts.Rollout) Workload {\n\treturn &rollout{r}\n}\n\n// RolloutImpl casts the given Object as an *argoRollout.Rollout and returns\n// it together with a status flag indicating whether the cast was possible.\nfunc RolloutImpl(o Object) (*argoRollouts.Rollout, bool) {\n\tif s, ok := o.(*rollout); ok {\n\t\treturn s.Rollout, true\n\t}\n\treturn nil, false\n}\n\nfunc GetReplicaSet(c context.Context, name, namespace string) (Workload, error) {\n\td, err := replicaSets(c, namespace).Get(c, name, meta.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &replicaSet{d}, nil\n}\n\n// ReplicaSets returns all replica sets found in the given Namespace.\nfunc ReplicaSets(c context.Context, namespace string, labelSelector labels.Set) ([]Workload, error) {\n\tls, err := replicaSets(c, namespace).List(c, listOptions(labelSelector))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tis := ls.Items\n\tos := make([]Workload, len(is))\n\tfor i := range is {\n\t\tos[i] = ReplicaSet(&is[i])\n\t}\n\treturn os, nil\n}\n\nfunc ReplicaSet(d *apps.ReplicaSet) Workload {\n\treturn &replicaSet{d}\n}\n\n// ReplicaSetImpl casts the given Object as an *apps.ReplicaSet and returns\n// it together with a status flag indicating whether the cast was possible.\nfunc ReplicaSetImpl(o Object) (*apps.ReplicaSet, bool) {\n\tif s, ok := o.(*replicaSet); ok {\n\t\treturn s.ReplicaSet, true\n\t}\n\treturn nil, false\n}\n\nfunc GetStatefulSet(c context.Context, name, namespace string) (Workload, error) {\n\td, err := statefulSets(c, namespace).Get(c, name, meta.GetOptions{})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &statefulSet{d}, nil\n}\n\n// StatefulSets returns all stateful sets found in the given Namespace.\nfunc StatefulSets(c context.Context, namespace string, labelSelector labels.Set) ([]Workload, error) {\n\tls, err := statefulSets(c, namespace).List(c, listOptions(labelSelector))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tis := ls.Items\n\tos := make([]Workload, len(is))\n\tfor i := range is {\n\t\tos[i] = StatefulSet(&is[i])\n\t}\n\treturn os, nil\n}\n\nfunc StatefulSet(d *apps.StatefulSet) Workload {\n\treturn &statefulSet{d}\n}\n\n// StatefulSetImpl casts the given Object as an *apps.StatefulSet and returns\n// it together with a status flag indicating whether the cast was possible.\nfunc StatefulSetImpl(o Object) (*apps.StatefulSet, bool) {\n\tif s, ok := o.(*statefulSet); ok {\n\t\treturn s.StatefulSet, true\n\t}\n\treturn nil, false\n}\n\nfunc String(o Object) string {\n\treturn fmt.Sprintf(\"%s %s.%s\", o.GetKind(), o.GetName(), o.GetNamespace())\n}\n\ntype deployment struct {\n\t*apps.Deployment\n}\n\nfunc deployments(c context.Context, namespace string) typedApps.DeploymentInterface {\n\treturn GetK8sInterface(c).AppsV1().Deployments(namespace)\n}\n\nfunc (o *deployment) ki(c context.Context) typedApps.DeploymentInterface {\n\treturn deployments(c, o.Namespace)\n}\n\nfunc (o *deployment) GetGroupResource() schema.GroupResource {\n\treturn schema.GroupResource{\n\t\tGroup:    o.TypeMeta.GroupVersionKind().Group,\n\t\tResource: \"deployments\",\n\t}\n}\n\nfunc (o *deployment) GetKind() Kind {\n\treturn DeploymentKind\n}\n\nfunc (o *deployment) Delete(c context.Context) error {\n\treturn o.ki(c).Delete(c, o.Name, meta.DeleteOptions{})\n}\n\nfunc (o *deployment) GetPodTemplate() *core.PodTemplateSpec {\n\treturn &o.Spec.Template\n}\n\nfunc (o *deployment) Patch(c context.Context, pt types.PatchType, data []byte, subresources ...string) error {\n\td, err := o.ki(c).Patch(c, o.Name, pt, data, meta.PatchOptions{}, subresources...)\n\tif err == nil {\n\t\to.Deployment = d\n\t}\n\treturn err\n}\n\nfunc (o *deployment) Refresh(c context.Context) error {\n\td, err := o.ki(c).Get(c, o.Name, meta.GetOptions{})\n\tif err == nil {\n\t\to.Deployment = d\n\t}\n\treturn err\n}\n\nfunc (o *deployment) Replicas() int {\n\treturn int(o.Status.Replicas)\n}\n\nfunc (o *deployment) Selector() (labels.Selector, error) {\n\treturn meta.LabelSelectorAsSelector(o.Spec.Selector)\n}\n\nfunc (o *deployment) String() string {\n\treturn String(o)\n}\n\nfunc (o *deployment) Update(c context.Context) error {\n\td, err := o.ki(c).Update(c, o.Deployment, meta.UpdateOptions{})\n\tif err == nil {\n\t\to.Deployment = d\n\t}\n\treturn err\n}\n\nfunc (o *deployment) Updated(origGeneration int64) bool {\n\tapplied := o.Generation >= origGeneration &&\n\t\to.Status.ObservedGeneration == o.Generation &&\n\t\t(o.Spec.Replicas == nil || o.Status.UpdatedReplicas >= *o.Spec.Replicas) &&\n\t\to.Status.UpdatedReplicas == o.Status.Replicas &&\n\t\to.Status.AvailableReplicas == o.Status.Replicas\n\treturn applied\n}\n\ntype rollout struct {\n\t*argoRollouts.Rollout\n}\n\nfunc rollouts(c context.Context, namespace string) typedArgoRollouts.RolloutInterface {\n\tari := GetArgoRolloutsInterface(c)\n\tif ari == nil {\n\t\treturn nil\n\t}\n\treturn ari.ArgoprojV1alpha1().Rollouts(namespace)\n}\n\nfunc (o *rollout) ki(c context.Context) typedArgoRollouts.RolloutInterface {\n\treturn rollouts(c, o.Namespace)\n}\n\nfunc (o *rollout) GetGroupResource() schema.GroupResource {\n\treturn schema.GroupResource{\n\t\tGroup:    o.TypeMeta.GroupVersionKind().Group,\n\t\tResource: \"rollouts\",\n\t}\n}\n\nfunc (o *rollout) GetKind() Kind {\n\treturn RolloutKind\n}\n\nfunc (o *rollout) Delete(c context.Context) error {\n\treturn o.ki(c).Delete(c, o.Name, meta.DeleteOptions{})\n}\n\nfunc (o *rollout) GetPodTemplate() *core.PodTemplateSpec {\n\treturn &o.Spec.Template\n}\n\nfunc (o *rollout) Patch(c context.Context, pt types.PatchType, data []byte, subresources ...string) error {\n\tr, err := o.ki(c).Patch(c, o.Name, pt, data, meta.PatchOptions{}, subresources...)\n\tif err == nil {\n\t\to.Rollout = r\n\t}\n\treturn err\n}\n\nfunc (o *rollout) Refresh(c context.Context) error {\n\td, err := o.ki(c).Get(c, o.Name, meta.GetOptions{})\n\tif err == nil {\n\t\to.Rollout = d\n\t}\n\treturn err\n}\n\nfunc (o *rollout) Replicas() int {\n\treturn int(o.Status.Replicas)\n}\n\nfunc (o *rollout) Selector() (labels.Selector, error) {\n\treturn meta.LabelSelectorAsSelector(o.Spec.Selector)\n}\n\nfunc (o *rollout) String() string {\n\treturn String(o)\n}\n\nfunc (o *rollout) Update(c context.Context) error {\n\td, err := o.ki(c).Update(c, o.Rollout, meta.UpdateOptions{})\n\tif err == nil {\n\t\to.Rollout = d\n\t}\n\treturn err\n}\n\nfunc (o *rollout) Updated(origGeneration int64) bool {\n\tapplied := o.Generation >= origGeneration &&\n\t\to.Status.ObservedGeneration == strconv.FormatInt(o.Generation, 10) &&\n\t\t(o.Spec.Replicas == nil || o.Status.UpdatedReplicas >= *o.Spec.Replicas) &&\n\t\to.Status.UpdatedReplicas == o.Status.Replicas &&\n\t\to.Status.AvailableReplicas == o.Status.Replicas\n\treturn applied\n}\n\ntype replicaSet struct {\n\t*apps.ReplicaSet\n}\n\nfunc replicaSets(c context.Context, namespace string) typedApps.ReplicaSetInterface {\n\treturn GetK8sInterface(c).AppsV1().ReplicaSets(namespace)\n}\n\nfunc (o *replicaSet) ki(c context.Context) typedApps.ReplicaSetInterface {\n\treturn replicaSets(c, o.Namespace)\n}\n\nfunc (o *replicaSet) GetGroupResource() schema.GroupResource {\n\treturn schema.GroupResource{\n\t\tGroup:    o.TypeMeta.GroupVersionKind().Group,\n\t\tResource: \"replicasets\",\n\t}\n}\n\nfunc (o *replicaSet) GetKind() Kind {\n\treturn ReplicaSetKind\n}\n\nfunc (o *replicaSet) Delete(c context.Context) error {\n\treturn o.ki(c).Delete(c, o.Name, meta.DeleteOptions{})\n}\n\nfunc (o *replicaSet) GetPodTemplate() *core.PodTemplateSpec {\n\treturn &o.Spec.Template\n}\n\nfunc (o *replicaSet) Patch(c context.Context, pt types.PatchType, data []byte, subresources ...string) error {\n\td, err := o.ki(c).Patch(c, o.Name, pt, data, meta.PatchOptions{}, subresources...)\n\tif err == nil {\n\t\to.ReplicaSet = d\n\t}\n\treturn err\n}\n\nfunc (o *replicaSet) Refresh(c context.Context) error {\n\td, err := o.ki(c).Get(c, o.Name, meta.GetOptions{})\n\tif err == nil {\n\t\to.ReplicaSet = d\n\t}\n\treturn err\n}\n\nfunc (o *replicaSet) Replicas() int {\n\treturn int(o.Status.Replicas)\n}\n\nfunc (o *replicaSet) Selector() (labels.Selector, error) {\n\treturn meta.LabelSelectorAsSelector(o.Spec.Selector)\n}\n\nfunc (o *replicaSet) String() string {\n\treturn String(o)\n}\n\nfunc (o *replicaSet) Update(c context.Context) error {\n\td, err := o.ki(c).Update(c, o.ReplicaSet, meta.UpdateOptions{})\n\tif err == nil {\n\t\to.ReplicaSet = d\n\t}\n\treturn err\n}\n\nfunc (o *replicaSet) Updated(origGeneration int64) bool {\n\tapplied := o.Generation >= origGeneration &&\n\t\to.Status.ObservedGeneration == o.Generation &&\n\t\t(o.Spec.Replicas == nil || o.Status.Replicas >= *o.Spec.Replicas) &&\n\t\to.Status.FullyLabeledReplicas == o.Status.Replicas &&\n\t\to.Status.AvailableReplicas == o.Status.Replicas\n\treturn applied\n}\n\ntype statefulSet struct {\n\t*apps.StatefulSet\n}\n\nfunc statefulSets(c context.Context, namespace string) typedApps.StatefulSetInterface {\n\treturn GetK8sInterface(c).AppsV1().StatefulSets(namespace)\n}\n\nfunc (o *statefulSet) ki(c context.Context) typedApps.StatefulSetInterface {\n\treturn statefulSets(c, o.Namespace)\n}\n\nfunc (o *statefulSet) GetGroupResource() schema.GroupResource {\n\treturn schema.GroupResource{\n\t\tGroup:    o.TypeMeta.GroupVersionKind().Group,\n\t\tResource: \"statefulsets\",\n\t}\n}\n\nfunc (o *statefulSet) GetKind() Kind {\n\treturn StatefulSetKind\n}\n\nfunc (o *statefulSet) Delete(c context.Context) error {\n\treturn o.ki(c).Delete(c, o.Name, meta.DeleteOptions{})\n}\n\nfunc (o *statefulSet) GetPodTemplate() *core.PodTemplateSpec {\n\treturn &o.Spec.Template\n}\n\nfunc (o *statefulSet) Patch(c context.Context, pt types.PatchType, data []byte, subresources ...string) error {\n\td, err := o.ki(c).Patch(c, o.Name, pt, data, meta.PatchOptions{}, subresources...)\n\tif err == nil {\n\t\to.StatefulSet = d\n\t}\n\treturn err\n}\n\nfunc (o *statefulSet) Refresh(c context.Context) error {\n\td, err := o.ki(c).Get(c, o.Name, meta.GetOptions{})\n\tif err == nil {\n\t\to.StatefulSet = d\n\t}\n\treturn err\n}\n\nfunc (o *statefulSet) Replicas() int {\n\treturn int(o.Status.Replicas)\n}\n\nfunc (o *statefulSet) Selector() (labels.Selector, error) {\n\treturn meta.LabelSelectorAsSelector(o.Spec.Selector)\n}\n\nfunc (o *statefulSet) String() string {\n\treturn String(o)\n}\n\nfunc (o *statefulSet) Update(c context.Context) error {\n\td, err := o.ki(c).Update(c, o.StatefulSet, meta.UpdateOptions{})\n\tif err == nil {\n\t\to.StatefulSet = d\n\t}\n\treturn err\n}\n\nfunc (o *statefulSet) Updated(origGeneration int64) bool {\n\tapplied := o.Generation >= origGeneration &&\n\t\to.Status.ObservedGeneration == o.Generation &&\n\t\t(o.Spec.Replicas == nil || o.Status.UpdatedReplicas >= *o.Spec.Replicas) &&\n\t\to.Status.UpdatedReplicas == o.Status.Replicas &&\n\t\to.Status.CurrentReplicas == o.Status.Replicas\n\treturn applied\n}\n"
  },
  {
    "path": "pkg/labels/selector.go",
    "content": "package labels\n\nimport (\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/selection\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nconst NameLabelKey = \"kubernetes.io/metadata.name\"\n\ntype Operator string\n\n// These are the camel-cased operators that are valid in a kubernetes namespaceSelector.\n//\n// NOTE! The lowercase variants in the k8s.io/apimachinery/pkg/selection are intended for\n// the selector string representation only. They are invalid in a YAML/JSON manifest!\nconst (\n\tOperatorIn        Operator = \"In\"\n\tOperatorNotIn     Operator = \"NotIn\"\n\tOperatorExists    Operator = \"Exists\"\n\tOperatorNotExists Operator = \"DoesNotExist\"\n)\n\nfunc (op Operator) String() string {\n\treturn string(op)\n}\n\nfunc (op Operator) AsSelectionOperator() selection.Operator {\n\treturn selection.Operator(strings.ToLower(string(op)))\n}\n\ntype Selector struct {\n\tMatchLabels      map[string]string `json:\"matchLabels,omitempty\"`\n\tMatchExpressions []*Requirement    `json:\"matchExpressions,omitempty\"`\n}\n\ntype Requirement struct {\n\tKey      string   `json:\"key\"`\n\tOperator Operator `json:\"operator\"`\n\tValues   []string `json:\"values\"`\n}\n\nfunc UnmarshalSelector(data []byte) (*Selector, error) {\n\tdata, err := yaml.YAMLToJSON(data)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn UnmarshalSelectorJSON(data)\n}\n\nfunc UnmarshalSelectorJSON(data []byte) (*Selector, error) {\n\tvar nsSelector Selector\n\terr := json.Unmarshal(data, &nsSelector)\n\treturn &nsSelector, err\n}\n\n// GetAllRequirements transforms the \"<key>=<value>\" MatchableLabels into \"<key> in [<value>]\"\n// MatchRequirements and returns the sorted sum of all requirements.\nfunc (sel *Selector) GetAllRequirements() []*Requirement {\n\tif sel == nil {\n\t\treturn nil\n\t}\n\tes := sel.MatchExpressions\n\tfor key, val := range sel.MatchLabels {\n\t\tes = append(es, &Requirement{\n\t\t\tKey:      key,\n\t\t\tOperator: OperatorIn,\n\t\t\tValues:   []string{val},\n\t\t})\n\t}\n\tslices.SortFunc(es, rqCmp)\n\treturn es\n}\n\nfunc (sel *Selector) Static() bool {\n\tif sel == nil {\n\t\treturn false\n\t}\n\tswitch len(sel.MatchLabels) {\n\tcase 0:\n\t\tif len(sel.MatchExpressions) == 1 {\n\t\t\tm := sel.MatchExpressions[0]\n\t\t\treturn m.Key == NameLabelKey && m.Operator == OperatorIn\n\t\t}\n\tcase 1:\n\t\t_, ok := sel.MatchLabels[NameLabelKey]\n\t\treturn ok\n\t}\n\treturn false\n}\n\nfunc (sel *Selector) StaticNames() []string {\n\tif sel == nil {\n\t\treturn nil\n\t}\n\tswitch len(sel.MatchLabels) {\n\tcase 0:\n\t\tif len(sel.MatchExpressions) == 1 {\n\t\t\tm := sel.MatchExpressions[0]\n\t\t\tif m.Key == NameLabelKey && m.Operator == OperatorIn {\n\t\t\t\treturn m.Values\n\t\t\t}\n\t\t}\n\tcase 1:\n\t\tif n, ok := sel.MatchLabels[NameLabelKey]; ok {\n\t\t\treturn []string{n}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (sel *Selector) LabelsSelector() (labels.Selector, error) {\n\treturn NewLabelsSelector(sel.GetAllRequirements())\n}\n\nfunc NewLabelsSelector(es []*Requirement) (labels.Selector, error) {\n\trqs := make([]labels.Requirement, 0, len(es))\n\tfor _, ns := range es {\n\t\trq, err := labels.NewRequirement(ns.Key, ns.Operator.AsSelectionOperator(), ns.Values)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\trqs = append(rqs, *rq)\n\t}\n\treturn labels.NewSelector().Add(rqs...), nil\n}\n\nfunc rqCmp(a, b *Requirement) int {\n\tn := strings.Compare(a.Key, b.Key)\n\tif n == 0 {\n\t\tn = strings.Compare(string(a.Operator), string(b.Operator))\n\t\tif n == 0 {\n\t\t\tn = slices.Compare(a.Values, b.Values)\n\t\t}\n\t}\n\treturn n\n}\n\nfunc SelectorFromNames(names ...string) *Selector {\n\tif len(names) == 0 {\n\t\treturn nil\n\t}\n\treturn &Selector{\n\t\tMatchExpressions: []*Requirement{{\n\t\t\tKey:      NameLabelKey,\n\t\t\tOperator: OperatorIn,\n\t\t\tValues:   names,\n\t\t}},\n\t}\n}\n"
  },
  {
    "path": "pkg/log/base_logger.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"log/slog\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/handler\"\n)\n\nfunc MakeBaseLogger(ctx context.Context, out io.Writer, logLevel string) context.Context {\n\topts := []handler.Option{\n\t\thandler.Output(out),\n\t\thandler.TimeFormat(\"2006-01-02 15:04:05.0000\"),\n\t\thandler.LevelEnabler(clog.TreeEnabled),\n\t}\n\tlvl := slog.LevelInfo\n\tif logLevel != \"\" {\n\t\tlvl = clog.MustParseLevel(logLevel)\n\t}\n\tctx = clog.WithTreeLevel(ctx, lvl)\n\treturn clog.WithLogger(ctx, slog.New(handler.NewText(opts...)))\n}\n"
  },
  {
    "path": "pkg/log/group.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\n// Group is a wrapper around [errgroup.Group] and a [context.Context].\ntype group struct {\n\tcontext.Context\n\t*errgroup.Group\n}\n\n// Group is a wrapper around [errgroup.Group] and a [context.Context]. It changes the semantics of the Go method to use the given\n// name as a [slog.Logger] group prefix.\ntype Group interface {\n\tcontext.Context\n\n\t// Go runs the given function in a goroutine where the context logger uses the given name as a group prefix.\n\tGo(name string, f func(context.Context) error)\n\n\t// Wait blocks until all function calls from the Go method have returned, then returns the first non-nil error (if any) from them.\n\tWait() error\n}\n\n// NewGroup returns a new Group with the given context.\nfunc NewGroup(ctx context.Context) Group {\n\tg := group{}\n\tg.Group, g.Context = errgroup.WithContext(ctx)\n\treturn g\n}\n\n// Go runs the given function in a goroutine where the context logger uses the given name as a group prefix.\nfunc (g group) Go(name string, f func(context.Context) error) {\n\tg.Group.Go(func() error { return f(clog.WithGroup(g.Context, name)) })\n}\n"
  },
  {
    "path": "pkg/log/timed_level.go",
    "content": "package log\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nconst UnsetLevel = slog.Level(math.MaxInt)\n\n// LevelSetter is a function that sets the log-level.\ntype LevelSetter func(ctx context.Context, logLevel slog.Level) bool\n\n// TimedLevel is an object capable of setting a log-level for a given time\n// period and then resetting it to a default.\ntype TimedLevel interface {\n\tsync.Locker\n\n\t// Get returns the current level and the time left until that level\n\t// is reset to default. An empty string and zero is returned if\n\t// no level has been set or if it has expired already.\n\tGet() (slog.Level, time.Duration)\n\n\t// Set sets a new log-level that will be active for the given duration. If the\n\t// duration is zero, then the log-level will be active until the next call to\n\t// Set. If level is the empty string, then duration is ignored and the log-level\n\t// will be reset to default.\n\tSet(ctx context.Context, level slog.Level, duration time.Duration)\n\n\t// Reset restores the log-level to its default value\n\tReset(ctx context.Context)\n}\n\ntype timedLevel struct {\n\tsync.Mutex\n\tsetter       LevelSetter\n\ttempLevel    slog.Level\n\tdefaultLevel slog.Level\n\ttimer        *time.Timer\n\texpires      *time.Time\n}\n\n// NewTimedLevel returns a new TimedLevel for the given default level and setter.\nfunc NewTimedLevel(defaultLevel slog.Level, setter LevelSetter) TimedLevel {\n\treturn &timedLevel{\n\t\tsetter:       setter,\n\t\ttempLevel:    UnsetLevel,\n\t\tdefaultLevel: defaultLevel,\n\t}\n}\n\nfunc (tl *timedLevel) Get() (slog.Level, time.Duration) {\n\ttl.Lock()\n\tdefer tl.Unlock()\n\tif tl.tempLevel == UnsetLevel || tl.expires == nil {\n\t\treturn tl.tempLevel, 0\n\t}\n\tremain := time.Until(*tl.expires)\n\tif remain <= 0 {\n\t\treturn UnsetLevel, 0\n\t}\n\treturn tl.tempLevel, remain\n}\n\nfunc (tl *timedLevel) Set(ctx context.Context, level slog.Level, duration time.Duration) {\n\tif level == UnsetLevel {\n\t\ttl.Reset(ctx)\n\t\treturn\n\t}\n\n\ttl.Lock()\n\tdefer tl.Unlock()\n\n\tif tl.timer != nil {\n\t\ttl.timer.Stop()\n\t}\n\n\tif tl.setter(ctx, level) {\n\t\tclog.Infof(ctx, \"Logging at this level %q\", clog.LevelWithTrace(level))\n\t}\n\ttl.tempLevel = level\n\tif duration == 0 {\n\t\ttl.expires = nil\n\t\ttl.timer = nil\n\t\treturn\n\t}\n\n\texTime := time.Now().Add(duration)\n\ttl.expires = &exTime\n\tif tl.timer == nil {\n\t\ttl.timer = time.AfterFunc(duration, func() {\n\t\t\ttl.Reset(ctx)\n\t\t})\n\t} else {\n\t\ttl.timer.Reset(duration)\n\t}\n}\n\n// Reset restores the log-level to its default value.\nfunc (tl *timedLevel) Reset(ctx context.Context) {\n\ttl.Lock()\n\tdefer tl.Unlock()\n\ttl.expires = nil\n\ttl.tempLevel = UnsetLevel\n\ttl.setter(ctx, tl.defaultLevel)\n}\n"
  },
  {
    "path": "pkg/maps/utils.go",
    "content": "package maps\n\nimport (\n\t\"cmp\"\n\t\"slices\"\n\t\"sort\"\n)\n\n// Copy creates a copy of the given map and returns it.\nfunc Copy[K comparable, V any](a map[K]V) map[K]V {\n\tc := make(map[K]V, len(a))\n\tfor k, v := range a {\n\t\tc[k] = v\n\t}\n\treturn c\n}\n\n// Equal returns true if the two maps contain the exact same set of associations.\nfunc Equal[K comparable, V comparable](a, b map[K]V) bool {\n\tif len(a) != len(b) {\n\t\treturn false\n\t}\n\tfor k, v := range a {\n\t\tif v != b[k] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Merge merges map src into dst, giving the entries in src higher priority.\nfunc Merge[K comparable, V any](dst, src map[K]V) {\n\tfor k, v := range src {\n\t\tdst[k] = v\n\t}\n}\n\n// KeySlice returns a slice containing the keys of the map m.\nfunc KeySlice[M ~map[K]V, K comparable, V any](m M) []K {\n\tr := make([]K, len(m))\n\ti := 0\n\tfor k := range m {\n\t\tr[i] = k\n\t\ti++\n\t}\n\treturn r\n}\n\n// SortedKeys returns the keys of the map m sorted alphabetically.\nfunc SortedKeys[M ~map[K]V, K cmp.Ordered, V any](m M) []K {\n\tr := KeySlice(m)\n\tslices.Sort(r)\n\treturn r\n}\n\n// ToSortedSlice returns a slice of the values in the given map, sorted by that map's keys.\nfunc ToSortedSlice[K cmp.Ordered, V any](m map[K]V) []V {\n\tns := make([]K, len(m))\n\ti := 0\n\tfor n := range m {\n\t\tns[i] = n\n\t\ti++\n\t}\n\tsort.Slice(ns, func(i, j int) bool { return ns[i] < ns[j] })\n\tvs := make([]V, i)\n\tfor i, n := range ns {\n\t\tvs[i] = m[n]\n\t}\n\treturn vs\n}\n\nfunc DeltaUpdate[K comparable, V any](m map[K]V, upserts map[K]V, removals []K) {\n\tfor k, v := range upserts {\n\t\tm[k] = v\n\t}\n\tfor _, k := range removals {\n\t\tdelete(m, k)\n\t}\n}\n\nfunc Values[K comparable, V any](m map[K]V) []V {\n\tml := len(m)\n\tvs := make([]V, ml)\n\tfor _, v := range m {\n\t\tml--\n\t\tvs[ml] = v\n\t}\n\treturn vs\n}\n"
  },
  {
    "path": "pkg/maps/xmap.go",
    "content": "package maps\n\nimport (\n\t\"time\"\n\n\t\"github.com/puzpuzpuz/xsync/v4\"\n)\n\n// GC removes entries from the provided xsync.Map at regular intervals based on a provided condition and stops when the done channel is closed.\nfunc GC[K comparable, V any](m *xsync.Map[K, V], interval time.Duration, done <-chan struct{}, deleteWhen func(K, V) bool) {\n\tticker := time.NewTicker(interval)\n\tfor {\n\t\tselect {\n\t\tcase <-done:\n\t\t\tticker.Stop()\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\tm.Range(func(k K, v V) bool {\n\t\t\t\tif deleteWhen(k, v) {\n\t\t\t\t\tm.Delete(k)\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/matcher/header_stringer.go",
    "content": "package matcher\n\nimport (\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n)\n\n// HeaderStringer turns a http.Header into a fmt.Stringer. It is useful when it's desired to defer string formatting of\n// the header depending on loglevel, for instance:\n//\n//\tclog.Debugf(c, \"Header = %s\", HeaderStringer(header))\n//\n// would not perform the actual formatting unless the loglevel is DEBUG or higher.\ntype HeaderStringer http.Header\n\n// String formats the Header to a comma-separated list of ordered key:value pairs.\nfunc (s HeaderStringer) String() string {\n\th := http.Header(s)\n\tsb := strings.Builder{}\n\tks := make([]string, len(h))\n\ti := 0\n\tfor k := range h {\n\t\tks[i] = k\n\t\ti++\n\t}\n\tsort.Strings(ks)\n\tfor i, k := range ks {\n\t\tif i > 0 {\n\t\t\tsb.WriteByte(',')\n\t\t}\n\t\tsb.WriteString(k)\n\t\tsb.WriteByte(':')\n\t\tfor p, v := range h[k] {\n\t\t\tif p > 0 {\n\t\t\t\tsb.WriteByte(';')\n\t\t\t}\n\t\t\tsb.WriteString(v)\n\t\t}\n\t}\n\treturn sb.String()\n}\n"
  },
  {
    "path": "pkg/matcher/header_stringer_test.go",
    "content": "package matcher\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestStringer_String(t *testing.T) {\n\tnewStringer := func(s ...string) HeaderStringer {\n\t\th := http.Header{}\n\t\tfor i := 0; i < len(s); i += 2 {\n\t\t\tk := s[i]\n\t\t\tvs := strings.Split(s[i+1], \";\") // THe semicolon is deliberate to ensure that comma is the result of formatting\n\t\t\th.Set(k, vs[0])\n\t\t\tfor i := 1; i < len(vs); i++ {\n\t\t\t\th.Add(k, vs[i])\n\t\t\t}\n\t\t}\n\t\treturn HeaderStringer(h)\n\t}\n\n\ttests := []struct {\n\t\tname string\n\t\ts    HeaderStringer\n\t\twant string\n\t}{\n\t\t{\n\t\t\t\"one header, single value\",\n\t\t\tnewStringer(\"hdr-one\", \"val\"),\n\t\t\t\"Hdr-One:val\",\n\t\t},\n\t\t{\n\t\t\t\"one header, multiple values\",\n\t\t\tnewStringer(\"hdr-one\", \"val1;val2;val3\"),\n\t\t\t\"Hdr-One:val1;val2;val3\",\n\t\t},\n\t\t{\n\t\t\t\"multiple headers, single value\",\n\t\t\tnewStringer(\"hdr-one\", \"val1;val2\", \"hdr-two\", \"the value\"),\n\t\t\t\"Hdr-One:val1;val2,Hdr-Two:the value\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.s.String(); got != tt.want {\n\t\t\t\tt.Errorf(\"String() = %q, want %q\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/matcher/headers.go",
    "content": "package matcher\n\nimport (\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/ioutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n)\n\ntype HeaderMap map[string]Value\n\n// Headers uses a set of Value matchers to match a http.Header.\ntype Headers interface {\n\t// Map returns the map correspondence of this instance. The returned value can be\n\t// used as an argument to NewHeaders to create an identical Headers.\n\tMap() map[string]string\n\n\t// HeaderMap returns HeaderMap correspondence of this instance.\n\tHeaderMap() HeaderMap\n\n\t// Matches returns true if all Value matchers in this instance are matched by the given http.Header.\n\t// Header name comparison is made using the textproto.CanonicalMIMEHeaderKey form of the keys.\n\tMatches(header http.Header) bool\n}\n\n// NewHeaders creates a new Headers with all header keys normalized to canonical MIME format.\n// HTTP headers are case-insensitive per RFC 7230, so we use the same canonicalization as net/http.\n//\n// Examples: \"x-user\" -> \"X-User\", \"content-type\" -> \"Content-Type\".\nfunc NewHeaders(hs map[string]string) Headers {\n\thm := make(HeaderMap, len(hs))\n\tfor k, v := range hs {\n\t\thm[textproto.CanonicalMIMEHeaderKey(k)] = NewValue(v)\n\t}\n\treturn hm\n}\n\n// Map returns the map correspondence of this instance. The returned value can be\n// used as an argument to NewHeaders to create an identical Headers.\nfunc (m HeaderMap) Map() map[string]string {\n\tr := make(map[string]string, len(m))\n\tfor k, v := range m {\n\t\tr[k] = v.String()\n\t}\n\treturn r\n}\n\n// HeaderMap returns the internal HeaderMap. Any modifications made to this map must\n// ensure that keys are canonicalized using textproto.CanonicalMIMEHeaderKey.\nfunc (m HeaderMap) HeaderMap() HeaderMap {\n\treturn m\n}\n\n// Matches returns true if all Value matchers in this instance are matched by the given http.Header.\n// Header name comparison is made using the textproto.CanonicalMIMEHeaderKey form of the keys.\nfunc (m HeaderMap) Matches(h http.Header) bool {\n\tfor name, vm := range m {\n\t\tif v := h.Get(name); !vm.Matches(v) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (m HeaderMap) String() string {\n\tsb := strings.Builder{}\n\tm.appendString(&sb, \"\")\n\treturn sb.String()\n}\n\nfunc (m HeaderMap) appendString(sb *strings.Builder, indent string) {\n\tif len(m) == 0 {\n\t\treturn\n\t}\n\tsb.WriteString(indent)\n\tsb.WriteString(\"header\")\n\tif len(m) > 1 {\n\t\tsb.WriteString(\"s\\n\")\n\t\tindent += \" \"\n\t} else {\n\t\tsb.WriteByte(' ')\n\t\tindent = \"\"\n\t}\n\tfirst := true\n\tfor _, k := range maps.SortedKeys(m) {\n\t\tif !first {\n\t\t\tsb.WriteByte('\\n')\n\t\t}\n\t\tfirst = false\n\t\tv := m[k]\n\t\top := v.Op()\n\t\tif op == \"==\" {\n\t\t\tioutil.Printf(sb, \"%s'%s: %s'\", indent, k, v)\n\t\t} else {\n\t\t\tioutil.Printf(sb, \"%s'%s %s %s'\", indent, k, v.Op(), v)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/matcher/headers_test.go",
    "content": "package matcher\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_headers_Matches(t *testing.T) {\n\theader := func(hm map[string]string) http.Header {\n\t\thd := make(http.Header, len(hm))\n\t\tfor k, v := range hm {\n\t\t\thd.Set(k, v)\n\t\t}\n\t\treturn hd\n\t}\n\n\ttests := []struct {\n\t\tname   string\n\t\tmatch  map[string]string\n\t\theader http.Header\n\t\twant   bool\n\t}{\n\t\t{\n\t\t\tname:   \"exact match\",\n\t\t\tmatch:  map[string]string{\"x-some-header\": \"some value\"},\n\t\t\theader: header(map[string]string{\"x-some-header\": \"some value\"}),\n\t\t\twant:   true,\n\t\t},\n\t\t{\n\t\t\tname:   \"canonical name match\",\n\t\t\tmatch:  map[string]string{\"X-Some-Header\": \"some value\"},\n\t\t\theader: header(map[string]string{\"x-some-header\": \"some value\"}),\n\t\t\twant:   true,\n\t\t},\n\t\t{\n\t\t\tname:   \"all-caps name match\",\n\t\t\tmatch:  map[string]string{\"X-SOME-HEADER\": \"some value\"},\n\t\t\theader: header(map[string]string{\"x-some-header\": \"some value\"}),\n\t\t\twant:   true,\n\t\t},\n\t\t{\n\t\t\tname:   \"case sensitive value mismatch\",\n\t\t\tmatch:  map[string]string{\"x-some-header\": \"Some Value\"},\n\t\t\theader: header(map[string]string{\"x-some-header\": \"some value\"}),\n\t\t\twant:   false,\n\t\t},\n\t\t{\n\t\t\tname:   \"regexp match\",\n\t\t\tmatch:  map[string]string{\"x-some-header\": \".*value\"},\n\t\t\theader: header(map[string]string{\"x-some-header\": \"some value\"}),\n\t\t\twant:   true,\n\t\t},\n\t\t{\n\t\t\tname:   \"regexp mismatch\",\n\t\t\tmatch:  map[string]string{\"x-some-header\": \".*values\"},\n\t\t\theader: header(map[string]string{\"x-some-header\": \"some value\"}),\n\t\t\twant:   false,\n\t\t},\n\t\t{\n\t\t\tname:   \"superfluous headers match\",\n\t\t\tmatch:  map[string]string{\"a\": \"1\"},\n\t\t\theader: header(map[string]string{\"a\": \"1\", \"b\": \"2\"}),\n\t\t\twant:   true,\n\t\t},\n\t\t{\n\t\t\tname:   \"missing headers mismatch\",\n\t\t\tmatch:  map[string]string{\"a\": \"1\", \"b\": \"2\"},\n\t\t\theader: header(map[string]string{\"a\": \"1\"}),\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\thm := NewHeaders(tt.match)\n\t\t\tassert.Equal(t, tt.want, hm.Matches(tt.header))\n\t\t})\n\t}\n}\n\nfunc Test_NewHeaders_error(t *testing.T) {\n\tm := NewHeaders(map[string]string{\"a\": \"un(balanced\"})\n\tv, ok := m.HeaderMap()[\"A\"]\n\trequire.True(t, ok)\n\tassert.Equal(t, v.Op(), ValueOpEqual)\n\tassert.Equal(t, v.String(), \"un(balanced\")\n}\n"
  },
  {
    "path": "pkg/matcher/paths.go",
    "content": "package matcher\n\nimport (\n\t\"slices\"\n\t\"strings\"\n)\n\ntype Paths []Value\n\nconst (\n\tPathEqual  = \":path-equal:\"\n\tPathPrefix = \":path-prefix:\"\n\tPathRegex  = \":path-regex:\"\n)\n\nfunc splitPath(path string) (string, string) {\n\tif len(path) > 0 && path[0] == ':' {\n\t\tnxt := strings.Index(path[1:], \":\")\n\t\tif nxt > 0 {\n\t\t\tnxt += 2\n\t\t\treturn path[:nxt], path[nxt:]\n\t\t}\n\t}\n\treturn PathEqual, path\n}\n\nfunc PathValue(path string) (pv Value) {\n\tp, v := splitPath(path)\n\tswitch p {\n\tcase PathPrefix:\n\t\tpv = NewPrefix(v)\n\tcase PathRegex:\n\t\tpv = NewRegex(v)\n\tdefault:\n\t\tpv = NewEqual(v)\n\t}\n\treturn pv\n}\n\nfunc NewPaths(paths []string) Paths {\n\tpvs := make(Paths, len(paths))\n\tfor i, pv := range paths {\n\t\tpvs[i] = PathValue(pv)\n\t}\n\treturn pvs\n}\n\nfunc (ps Paths) Slice() []string {\n\tss := make([]string, len(ps))\n\tfor i, p := range ps {\n\t\tvar pfx string\n\t\tswitch p.Op() {\n\t\tcase ValueOpRegex:\n\t\t\tpfx = PathRegex\n\t\tcase ValueOpPrefix:\n\t\t\tpfx = PathPrefix\n\t\tdefault:\n\t\t\tpfx = PathEqual\n\t\t}\n\t\tss[i] = pfx + p.String()\n\t}\n\treturn ss\n}\n\n// Matches returns true if at least one of the paths in this instance matches the given path\n// or if this instance is empty.\nfunc (ps Paths) Matches(path string) bool {\n\treturn len(ps) == 0 || slices.ContainsFunc(ps, func(v Value) bool { return v.Matches(path) })\n}\n\nfunc (ps Paths) String() string {\n\tsb := strings.Builder{}\n\tps.appendString(&sb, \"\")\n\treturn sb.String()\n}\n\nfunc (ps Paths) appendString(sb *strings.Builder, indent string) {\n\tif len(ps) == 0 {\n\t\treturn\n\t}\n\tsb.WriteString(indent)\n\tsb.WriteString(\"path\")\n\tif len(ps) > 1 {\n\t\tsb.WriteString(\"s\\n\")\n\t\tindent += \" \"\n\t} else {\n\t\tsb.WriteByte(' ')\n\t\tindent = \"\"\n\t}\n\tfor i, p := range ps {\n\t\tif i > 0 {\n\t\t\tsb.WriteByte('\\n')\n\t\t}\n\t\tsb.WriteString(indent)\n\t\tsb.WriteString(string(p.Op()))\n\t\tsb.WriteByte(' ')\n\t\tsb.WriteString(p.String())\n\t}\n}\n"
  },
  {
    "path": "pkg/matcher/request.go",
    "content": "package matcher\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/textproto\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n)\n\n// The Request matcher uses a Value matcher and a Headers matcher to match the path and headers of a http request.\ntype Request interface {\n\tfmt.Stringer\n\n\t// Headers returns Headers of this instance.\n\tHeaders() Headers\n\n\t// IsGlobal returns true if this instance matches all requests.\n\tIsGlobal() bool\n\n\t// Map returns the map correspondence of this instance. The returned value can be\n\t// used as an argument to NewRequest to create an identical Request.\n\tMap() map[string]string\n\n\t// Matches returns true if given http.Request is matched.\n\tMatches(req *http.Request) bool\n\n\t// MatchesPathAndHeader returns true if both the path Headers match.\n\tMatchesPathAndHeader(path string, req http.Header) bool\n\n\t// Paths return the path matchers.\n\tPaths() Paths\n}\n\ntype request struct {\n\tpaths   Paths\n\theaders HeaderMap\n}\n\n// NewRequestFromMap creates a new Request based on the values of the given map. Aside from http headers,\n// the map may contain a :path: entry with a semicolon delimited list of paths. Each path should be prefixed\n// with one of three special keys.\n//\n//\t:path-equal: path will match if equal to the value\n//\t:path-prefix: path will match prefixed by the value\n//\t:path-regex: path will match it matches the regexp value\nfunc NewRequestFromMap(m map[string]string) Request {\n\tif len(m) == 0 {\n\t\treturn &request{}\n\t}\n\tvar ps Paths\n\tvar hm HeaderMap\n\tfor k, v := range m {\n\t\tswitch k {\n\t\tcase \":paths:\":\n\t\t\tps = NewPaths(strings.Split(v, \";\"))\n\t\tdefault:\n\t\t\tvm := NewValue(v)\n\t\t\tif hm == nil {\n\t\t\t\thm = make(HeaderMap)\n\t\t\t}\n\t\t\thm[textproto.CanonicalMIMEHeaderKey(k)] = vm\n\t\t}\n\t}\n\treturn &request{paths: ps, headers: hm}\n}\n\nfunc NewRequest(paths []string, hdrs map[string]string) Request {\n\trv := new(request)\n\tif len(paths) > 0 {\n\t\trv.paths = NewPaths(paths)\n\t}\n\tif len(hdrs) > 0 {\n\t\trv.headers = NewHeaders(hdrs).HeaderMap()\n\t}\n\treturn rv\n}\n\n// Map returns the map correspondence of this instance. The returned value can be\n// used as an argument to NewRequest to create an identical Request.\nfunc (r *request) Map() map[string]string {\n\tvar m map[string]string\n\tif len(r.headers) > 0 {\n\t\tm = r.headers.Map()\n\t}\n\tif len(r.paths) > 0 {\n\t\tpm := map[string]string{\":paths:\": strings.Join(r.paths.Slice(), \";\")}\n\t\tmaps.Merge(pm, m)\n\t\tm = pm\n\t}\n\treturn m\n}\n\n// Headers returns Headers of this instance.\nfunc (r *request) Headers() Headers {\n\treturn r.headers\n}\n\nfunc (r *request) IsGlobal() bool {\n\treturn len(r.paths) == 0 && len(r.headers) == 0\n}\n\n// Matches returns true if both the path Value matcher and the Headers matcher in this instance are\n// matched by the given http.Request.\nfunc (r *request) Matches(req *http.Request) bool {\n\treturn r.MatchesPathAndHeader(req.URL.Path, req.Header)\n}\n\nfunc (r *request) MatchesPathAndHeader(path string, header http.Header) bool {\n\treturn r.paths.Matches(path) && (len(r.headers) == 0 || r.headers.Matches(header))\n}\n\n// Paths return the path matchers.\nfunc (r *request) Paths() Paths {\n\treturn r.paths\n}\n\nfunc (r *request) String() string {\n\tif r.IsGlobal() {\n\t\treturn \"all TCP connections\"\n\t}\n\tsb := strings.Builder{}\n\tsb.WriteString(\"HTTP requests with\")\n\tswitch len(r.paths) {\n\tcase 0:\n\t\tsb.WriteByte(' ')\n\t\tr.headers.appendString(&sb, \"\")\n\tcase 1:\n\t\tif len(r.headers) < 2 {\n\t\t\tsb.WriteByte(' ')\n\t\t\tr.paths.appendString(&sb, \"\")\n\t\t\tif len(r.headers) > 0 {\n\t\t\t\tsb.WriteString(\" and \")\n\t\t\t\tr.headers.appendString(&sb, \"\")\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\tfallthrough\n\tdefault:\n\t\tif len(r.headers) > 0 {\n\t\t\tsb.WriteByte('\\n')\n\t\t\tr.paths.appendString(&sb, \" \")\n\t\t\tsb.WriteByte('\\n')\n\t\t\tr.headers.appendString(&sb, \" \")\n\t\t} else {\n\t\t\tsb.WriteByte(' ')\n\t\t\tr.paths.appendString(&sb, \"\")\n\t\t}\n\t}\n\treturn sb.String()\n}\n"
  },
  {
    "path": "pkg/matcher/request_test.go",
    "content": "package matcher\n\nimport (\n\t\"net/http\"\n\t\"regexp\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestNewRequest(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targs map[string]string\n\t\twant Request\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\targs: nil,\n\t\t\twant: &request{},\n\t\t},\n\t\t{\n\t\t\tname: \"path-equal\",\n\t\t\targs: map[string]string{\":paths:\": \":path-equal:/some/path\"},\n\t\t\twant: &request{paths: Paths{NewEqual(\"/some/path\")}},\n\t\t},\n\t\t{\n\t\t\tname: \"path-prefix\",\n\t\t\targs: map[string]string{\":paths:\": \":path-prefix:/some/path\"},\n\t\t\twant: &request{paths: Paths{NewPrefix(\"/some/path\")}},\n\t\t},\n\t\t{\n\t\t\tname: \"path-regex\",\n\t\t\targs: map[string]string{\":paths:\": \":path-regex:.*/path\"},\n\t\t\twant: &request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}},\n\t\t},\n\t\t{\n\t\t\tname: \"path-regex and headers\",\n\t\t\targs: map[string]string{\":paths:\": \":path-regex:.*/path\", \"A\": \"b\"},\n\t\t\twant: &request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}, headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\")})},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot := NewRequestFromMap(tt.args)\n\t\t\tassert.Equalf(t, tt.want, got, \"NewRequest(%v)\", tt.args)\n\t\t})\n\t}\n}\n\nfunc Test_request_Map(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\trequest request\n\t\twant    map[string]string\n\t}{\n\t\t{\n\t\t\t\"empty\",\n\t\t\trequest{},\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"path-equal\",\n\t\t\trequest{paths: Paths{NewEqual(\"/some/path\")}},\n\t\t\tmap[string]string{\":paths:\": \":path-equal:/some/path\"},\n\t\t},\n\t\t{\n\t\t\t\"path-prefix\",\n\t\t\trequest{paths: Paths{NewPrefix(\"/some/path\")}},\n\t\t\tmap[string]string{\":paths:\": \":path-prefix:/some/path\"},\n\t\t},\n\t\t{\n\t\t\t\"path-regex\",\n\t\t\trequest{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}},\n\t\t\tmap[string]string{\":paths:\": \":path-regex:.*/path\"},\n\t\t},\n\t\t{\n\t\t\t\"path-regex and headers\",\n\t\t\trequest{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}, headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\")})},\n\t\t\tmap[string]string{\":paths:\": \":path-regex:.*/path\", \"A\": \"b\"},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, tt.want, tt.request.Map(), \"Map()\")\n\t\t})\n\t}\n}\n\nfunc Test_request_Matches(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\trequest request\n\t\tpath    string\n\t\theaders http.Header\n\t\twant    bool\n\t}{\n\t\t{\n\t\t\tname:    \"empty\",\n\t\t\trequest: request{},\n\t\t\tpath:    \"/some/path\",\n\t\t\theaders: http.Header(map[string][]string{\"A\": {\"b\"}}),\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"path and headers\",\n\t\t\trequest: request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}, headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\")})},\n\t\t\tpath:    \"/some/path\",\n\t\t\theaders: http.Header(map[string][]string{\"A\": {\"b\"}}),\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"path and headers mismatch on just path\",\n\t\t\trequest: request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}, headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\")})},\n\t\t\tpath:    \"/some/path\",\n\t\t\theaders: nil,\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"path and headers mismatch on just headers\",\n\t\t\trequest: request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}, headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\")})},\n\t\t\tpath:    \"\",\n\t\t\theaders: http.Header(map[string][]string{\"A\": {\"b\"}}),\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"path-equal\",\n\t\t\trequest: request{paths: Paths{NewEqual(\"/some/path\")}},\n\t\t\tpath:    \"/some/path\",\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"path-equal mismatch\",\n\t\t\trequest: request{paths: Paths{NewEqual(\"/some\")}},\n\t\t\tpath:    \"/some/path\",\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"path-prefix\",\n\t\t\trequest: request{paths: Paths{NewPrefix(\"/some\")}},\n\t\t\tpath:    \"/some/path\",\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"path-prefix mismatch\",\n\t\t\trequest: request{paths: Paths{NewPrefix(\"/some\")}},\n\t\t\tpath:    \"/other/path\",\n\t\t\twant:    false,\n\t\t},\n\t\t{\n\t\t\tname:    \"path-regex\",\n\t\t\trequest: request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}},\n\t\t\tpath:    \"/some/path\",\n\t\t\twant:    true,\n\t\t},\n\t\t{\n\t\t\tname:    \"path-regex mismatch\",\n\t\t\trequest: request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}},\n\t\t\tpath:    \"/some/road\",\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\tassert.Equalf(t, tt.want, tt.request.MatchesPathAndHeader(tt.path, tt.headers), \"Matches(%v, %v)\", tt.path, tt.headers)\n\t\t})\n\t}\n}\n\nfunc Test_request_String(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\trequest request\n\t\twant    string\n\t}{\n\t\t{\n\t\t\tname: \"empty\",\n\t\t\twant: \"all TCP connections\",\n\t\t},\n\t\t{\n\t\t\tname:    \"path-equal\",\n\t\t\trequest: request{paths: Paths{NewEqual(\"/some/path\")}},\n\t\t\twant:    \"HTTP requests with path == /some/path\",\n\t\t},\n\t\t{\n\t\t\tname:    \"path-prefix\",\n\t\t\trequest: request{paths: Paths{NewPrefix(\"/some/path\")}},\n\t\t\twant:    \"HTTP requests with path prefix /some/path\",\n\t\t},\n\t\t{\n\t\t\tname:    \"path-regex\",\n\t\t\trequest: request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}},\n\t\t\twant:    \"HTTP requests with path =~ .*/path\",\n\t\t},\n\t\t{\n\t\t\tname:    \"headers\",\n\t\t\trequest: request{headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\")})},\n\t\t\twant:    \"HTTP requests with header 'A: b'\",\n\t\t},\n\t\t{\n\t\t\tname:    \"path and header\",\n\t\t\trequest: request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}, headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\")})},\n\t\t\twant:    \"HTTP requests with path =~ .*/path and header 'A: b'\",\n\t\t},\n\t\t{\n\t\t\tname:    \"path and headers\",\n\t\t\trequest: request{paths: Paths{rxValue{regexp.MustCompile(\".*/path\")}}, headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\"), \"B\": NewEqual(\"c\")})},\n\t\t\twant:    \"HTTP requests with\\n path =~ .*/path\\n headers\\n  'A: b'\\n  'B: c'\",\n\t\t},\n\t\t{\n\t\t\tname:    \"paths and headers\",\n\t\t\trequest: request{paths: Paths{NewPrefix(\"/some/path\"), rxValue{regexp.MustCompile(\".*/path\")}}, headers: HeaderMap(map[string]Value{\"A\": NewEqual(\"b\"), \"B\": NewEqual(\"c\")})},\n\t\t\twant:    \"HTTP requests with\\n paths\\n  prefix /some/path\\n  =~ .*/path\\n headers\\n  'A: b'\\n  'B: c'\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, tt.want, tt.request.String(), \"String()\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/matcher/value.go",
    "content": "package matcher\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\ntype ValueOp string\n\nconst (\n\tValueOpEqual  ValueOp = \"==\"\n\tValueOpRegex  ValueOp = \"=~\"\n\tValueOpPrefix ValueOp = \"prefix\"\n)\n\n// Value comes in three flavors. One that performs an exact match against a string, one that\n// uses a regular expression, and one that uses prefix matching.\ntype Value interface {\n\tfmt.Stringer\n\n\t// Matches returns true if the given string matches this Value\n\tMatches(value string) bool\n\n\t// Op returns either ==, =~, or prefix\n\tOp() ValueOp\n}\n\ntype textValue string\n\nfunc (t textValue) Matches(value string) bool {\n\treturn string(t) == value\n}\n\nfunc (t textValue) String() string {\n\treturn string(t)\n}\n\nfunc (t textValue) Op() ValueOp {\n\treturn ValueOpEqual\n}\n\ntype rxValue struct {\n\t*regexp.Regexp\n}\n\nfunc (r rxValue) Matches(value string) bool {\n\treturn value != \"\" && r.MatchString(value)\n}\n\nfunc (r rxValue) Op() ValueOp {\n\treturn ValueOpRegex\n}\n\ntype prefixValue string\n\nfunc (p prefixValue) Matches(value string) bool {\n\treturn strings.HasPrefix(value, string(p))\n}\n\nfunc (p prefixValue) String() string {\n\treturn string(p)\n}\n\nfunc (p prefixValue) Op() ValueOp {\n\treturn ValueOpPrefix\n}\n\n// NewValue returns a Value that is either an exact or a regexp matcher. The latter is chosen\n// when the given string contains regexp meta-characters.\nfunc NewValue(v string) Value {\n\tif regexp.QuoteMeta(v) == v {\n\t\treturn NewEqual(v)\n\t}\n\treturn NewRegex(v)\n}\n\n// NewOpValue returns a Value that is a matcher of the given type. If the given type is not\n// supported, an exact matcher is returned.\nfunc NewOpValue(op ValueOp, v string) Value {\n\tswitch op {\n\tcase ValueOpRegex:\n\t\treturn NewRegex(v)\n\tcase ValueOpPrefix:\n\t\treturn NewPrefix(v)\n\tdefault:\n\t\treturn NewEqual(v)\n\t}\n}\n\n// NewRegex returns a Value that is a regexp matcher. Returns an exact value if the string cannot be\n// compiled into a regexp.\nfunc NewRegex(v string) Value {\n\trx, err := regexp.Compile(v)\n\tif err != nil {\n\t\t// Treat an invalid regexp as an exact match\n\t\treturn NewEqual(v)\n\t}\n\treturn rxValue{rx}\n}\n\n// NewPrefix returns a Value that is a prefix matcher.\nfunc NewPrefix(v string) Value {\n\treturn prefixValue(v)\n}\n\n// NewEqual returns a Value that is an equal matcher.\nfunc NewEqual(v string) Value {\n\treturn textValue(v)\n}\n"
  },
  {
    "path": "pkg/pprof/pprof.go",
    "content": "package pprof\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t_ \"net/http/pprof\"\n)\n\nfunc PprofServer(ctx context.Context, port uint16) error {\n\tsvc := http.Server{\n\t\tBaseContext: func(_ net.Listener) context.Context { return ctx },\n\t\tAddr:        fmt.Sprintf(\"localhost:%d\", port),\n\t}\n\tgo func() {\n\t\t_ = svc.ListenAndServe()\n\t}()\n\t<-ctx.Done()\n\treturn svc.Shutdown(context.Background())\n}\n"
  },
  {
    "path": "pkg/proc/cmd.go",
    "content": "package proc\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"os/exec\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n)\n\n// StdCommand returns a command that redirects stdout and stderr to dos.Stdout and dos.Stderr\n// and performs no logging.\nfunc StdCommand(ctx context.Context, exe string, args ...string) *exec.Cmd {\n\tcmd := CommandContext(ctx, exe, args...)\n\tcmd.Stdout = dos.Stdout(ctx)\n\tcmd.Stderr = dos.Stderr(ctx)\n\tclog.Debug(ctx, shellquote.ShellString(exe, args))\n\treturn cmd\n}\n\n// CaptureErr disables command logging, captures Stdout and Stderr to two different buffers.\n// If an error occurs, the stdout output is discarded and the stderr output is included in the\n// returned error unless the error itself already contains that output.\n// On success, any output on stderr is discarded and the stdout output is returned.\nfunc CaptureErr(cmd *exec.Cmd) ([]byte, error) {\n\tvar stdOut, stdErr bytes.Buffer\n\tcmd.Stdout = &stdOut\n\tcmd.Stderr = &stdErr\n\tif err := cmd.Run(); err != nil {\n\t\tif es := strings.TrimSpace(stdErr.String()); es != \"\" {\n\t\t\tif !strings.Contains(err.Error(), es) {\n\t\t\t\terr = fmt.Errorf(\"%s: %w\", es, err)\n\t\t\t}\n\t\t}\n\t\treturn nil, err\n\t}\n\treturn stdOut.Bytes(), nil\n}\n"
  },
  {
    "path": "pkg/proc/docker_linux.go",
    "content": "//go:build linux\n\npackage proc\n\nimport (\n\t\"os\"\n)\n\nvar runningInContainer bool //nolint:gochecknoglobals // this is a constant\n\nfunc init() {\n\t_, err := os.Stat(\"/.dockerenv\")\n\trunningInContainer = err == nil\n}\n\n// RunningInContainer returns true if the current process runs from inside a docker container.\nfunc RunningInContainer() bool {\n\treturn runningInContainer\n}\n\n// SetRunningInContainer forces the runningInContainer state.\nfunc SetRunningInContainer(flag bool) {\n\trunningInContainer = flag\n}\n"
  },
  {
    "path": "pkg/proc/docker_other.go",
    "content": "//go:build !linux\n\npackage proc\n\n// RunningInContainer returns true if the current process runs from inside a docker container.\nfunc RunningInContainer() bool {\n\treturn false\n}\n\nfunc SetRunningInContainer(_ bool) {\n}\n"
  },
  {
    "path": "pkg/proc/exec.go",
    "content": "package proc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\" //nolint:depguard // We want no logging and no soft-context signal handling\n\t\"os/signal\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n)\n\n// Start will start the given executable with given args and env, and return the command. The signals are\n// dispatched as appropriate for the given platform (SIGTERM and SIGINT on Unix platforms\n// and os.Interrupt on Windows).\nfunc Start(ctx context.Context, env map[string]string, exe string, args ...string) (*exec.Cmd, error) {\n\tcmd := CommandStd(ctx, env, exe, args...)\n\treturn cmd, StartCmd(ctx, cmd)\n}\n\n// CommandStd will create a command based on the given executable with given args and env, and return the command.\n// The signals are dispatched as appropriate for the given platform (SIGTERM and SIGINT on Unix platforms\n// and os.Interrupt on Windows).\nfunc CommandStd(ctx context.Context, env map[string]string, exe string, args ...string) *exec.Cmd {\n\tcmd := CommandContext(ctx, exe, args...)\n\tcmd.Stdout = dos.Stdout(ctx)\n\tcmd.Stderr = dos.Stderr(ctx)\n\tcmd.Stdin = dos.Stdin(ctx)\n\tcmd.Env = dos.Environ(ctx)\n\tfor k, v := range env {\n\t\tcmd.Env = append(cmd.Env, k+\"=\"+v)\n\t}\n\treturn cmd\n}\n\n// StartCmd will run the given command with debug logging.\nfunc StartCmd(ctx context.Context, cmd *exec.Cmd) error {\n\tclog.Debug(ctx, shellquote.ShellArgsString(cmd.Args))\n\tif err := cmd.Start(); err != nil {\n\t\treturn fmt.Errorf(\"%s: %w\", shellquote.ShellArgsString(cmd.Args), err)\n\t}\n\treturn nil\n}\n\n// Wait will wait for the Process of the command to finish.\n// If cancel is not nil, Wait will listen for os signals and call cancel when it\n// receives one.\nfunc Wait(ctx context.Context, cancel context.CancelFunc, cmd *exec.Cmd) error {\n\tp := cmd.Process\n\tif p == nil {\n\t\treturn nil\n\t}\n\n\t// Ensure that appropriate signals terminates the context.\n\tif cancel != nil {\n\t\tsigCh := make(chan os.Signal, 1)\n\t\tsignal.Notify(sigCh, SignalsToForward...)\n\t\tdefer func() {\n\t\t\tsignal.Stop(sigCh)\n\t\t\tclose(sigCh)\n\t\t}()\n\t\tgo func() {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\tcase sig := <-sigCh:\n\t\t\t\tif sig == nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t_ = Terminate(p)\n\t\t\t\tcancel()\n\t\t\t}\n\t\t}()\n\t}\n\n\ts, err := p.Wait()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"%s: %w\", shellquote.ShellString(cmd.Path, cmd.Args), err)\n\t}\n\n\texitCode := s.ExitCode()\n\tif exitCode != 0 {\n\t\treturn fmt.Errorf(\"%s: exited with %d\", shellquote.ShellString(cmd.Path, cmd.Args), exitCode)\n\t}\n\treturn nil\n}\n\n// CreateNewProcessGroup ensures that the process uses a process group of its own to prevent\n// it getting affected by <ctrl-c> in the terminal.\nfunc CreateNewProcessGroup(cmd *exec.Cmd) {\n\tcreateNewProcessGroup(cmd)\n}\n\nfunc KillProcessGroup(ctx context.Context, cmd *exec.Cmd, signal os.Signal) {\n\tkillProcessGroup(ctx, cmd, signal)\n}\n\n// Run will run the given executable with given args and env, wait for it to terminate, and return\n// the result. The run will dispatch signals as appropriate for the given platform (SIGTERM and SIGINT on Unix platforms\n// and os.Interrupt on Windows).\nfunc Run(ctx context.Context, env map[string]string, exe string, args ...string) error {\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\n\tcmd, err := Start(ctx, env, exe, args...)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn Wait(ctx, cancel, cmd)\n}\n\nfunc StartInBackground(includeEnv bool, args ...string) error {\n\treturn startInBackground(includeEnv, args...)\n}\n\nfunc StartInBackgroundAsRoot(ctx context.Context, args ...string) error {\n\treturn startInBackgroundAsRoot(ctx, args...)\n}\n\nfunc IsAdmin() bool {\n\treturn isAdmin()\n}\n\nfunc Terminate(p *os.Process) error {\n\treturn terminate(p)\n}\n"
  },
  {
    "path": "pkg/proc/exec_unix.go",
    "content": "//go:build !windows\n\npackage proc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\" //nolint:depguard // We want no logging and no soft-context signal handling\n\t\"slices\"\n\t\"strings\"\n\n\t\"golang.org/x/sys/unix\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n)\n\nconst SIGTERM = unix.SIGTERM\n\nvar CommandContext = exec.CommandContext //nolint:gochecknoglobals // OS-specific function replacement\n\nvar SignalsToForward = []os.Signal{unix.SIGINT, unix.SIGTERM} //nolint:gochecknoglobals // OS-specific constant list\n\nfunc isAdmin() bool {\n\treturn os.Geteuid() == 0\n}\n\nfunc startInBackground(includeEnv bool, args ...string) error {\n\tcmd := exec.Command(args[0], args[1:]...)\n\tif includeEnv {\n\t\tcmd.Env = os.Environ()\n\t}\n\n\tcreateNewProcessGroup(cmd)\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn fmt.Errorf(\"%s: %w\", shellquote.ShellString(args[0], args[1:]), err)\n\t}\n\tif err := cmd.Process.Release(); err != nil {\n\t\treturn fmt.Errorf(\"%s: %w\", shellquote.ShellString(args[0], args[1:]), err)\n\t}\n\n\treturn nil\n}\n\nfunc startInBackgroundAsRoot(ctx context.Context, args ...string) error {\n\tif isAdmin() {\n\t\treturn startInBackground(false, args...)\n\t}\n\t// Run sudo with a prompt explaining why root credentials are needed.\n\tconst promptContext = \"telepresence network daemon\"\n\terr := exec.Command(\"sudo\", append([]string{\"-b\", \"-p\", fmt.Sprintf(\"[sudo: %s] Password: \", promptContext)}, args...)...).Run()\n\tif err == nil {\n\t\treturn nil\n\t}\n\t// Are we using sudo-rs?\n\tout, err2 := exec.Command(\"sudo\", \"--version\").CombinedOutput()\n\tif err2 != nil || !strings.Contains(string(out), \"sudo-rs\") {\n\t\t// return the original error\n\t\treturn err\n\t}\n\t// sudo-rs does not support the \"-b\" (background) option. We need it to start a shell command that backgrounds the executable\n\t// once the user has entered their password.\n\tcmdString := shellquote.ShellArgsString(slices.Insert(args, 0, \"setsid\")) + \"</dev/null >/dev/null 2>&1 &\"\n\treturn exec.Command(\"sudo\", \"-p\", promptContext, \"sh\", \"-c\", cmdString).Run()\n}\n\nfunc terminate(p *os.Process) error {\n\t// SIGTERM makes it through a PTY, SIGINT doesn't. Not sure why that is.\n\t// thallgren\n\treturn p.Signal(unix.SIGTERM)\n}\n\nfunc createNewProcessGroup(cmd *exec.Cmd) {\n\tcmd.SysProcAttr = &unix.SysProcAttr{\n\t\tSetpgid: true,\n\t}\n}\n\nfunc killProcessGroup(_ context.Context, cmd *exec.Cmd, signal os.Signal) {\n\tif p := cmd.Process; p != nil {\n\t\t_ = unix.Kill(-p.Pid, signal.(unix.Signal))\n\t}\n}\n"
  },
  {
    "path": "pkg/proc/exec_windows.go",
    "content": "package proc\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\" //nolint:depguard // We want no logging and no soft-context signal handling\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/windows\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/shellquote\"\n)\n\nvar SignalsToForward = []os.Signal{os.Interrupt} //nolint:gochecknoglobals // OS-specific constant list\n\n// SIGTERM uses os.Interrupt on Windows as a best effort.\nvar SIGTERM = os.Interrupt //nolint:gochecknoglobals // OS-specific constant\n\nfunc CommandContext(ctx context.Context, name string, args ...string) *exec.Cmd {\n\tcmd := exec.CommandContext(ctx, name, args...)\n\tcreateNewProcessGroup(cmd)\n\treturn cmd\n}\n\nfunc createNewProcessGroup(cmd *exec.Cmd) {\n\tcmd.SysProcAttr = &windows.SysProcAttr{CreationFlags: windows.CREATE_NEW_PROCESS_GROUP}\n}\n\nfunc startInBackground(_ bool, args ...string) error {\n\treturn shellExec(\"open\", args[0], args[1:]...)\n}\n\nfunc startInBackgroundAsRoot(_ context.Context, args ...string) error {\n\tverb := \"runas\"\n\tif isAdmin() {\n\t\tverb = \"open\"\n\t}\n\treturn shellExec(verb, args[0], args[1:]...)\n}\n\nfunc shellExec(verb, exe string, args ...string) error {\n\tcwd, _ := os.Getwd()\n\t// UTF16PtrFromString can only fail if the argument contains a NUL byte. That will never happen here.\n\tverbPtr, _ := windows.UTF16PtrFromString(verb)\n\texePtr, _ := windows.UTF16PtrFromString(exe)\n\tcwdPtr, _ := windows.UTF16PtrFromString(cwd)\n\tvar argPtr *uint16\n\tif len(args) > 0 {\n\t\targsStr := shellquote.ShellArgsString(args)\n\t\targPtr, _ = windows.UTF16PtrFromString(argsStr)\n\t}\n\treturn windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, windows.SW_HIDE)\n}\n\nfunc isAdmin() bool {\n\tvar t windows.Token\n\tif err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY, &t); err != nil {\n\t\treturn false\n\t}\n\te := t.IsElevated()\n\tt.Close()\n\treturn e\n}\n\nfunc terminate(p *os.Process) error {\n\treturn p.Kill()\n}\n\nconst peSize = uint32(unsafe.Sizeof(windows.ProcessEntry32{}))\n\ntype processInfo struct {\n\tpid  uint32\n\tppid uint32\n\texe  string\n}\n\nfunc killProcessGroup(ctx context.Context, cmd *exec.Cmd, sig os.Signal) {\n\tpes := make([]*processInfo, 0, 100)\n\terr := eachProcess(func(pe *windows.ProcessEntry32) bool {\n\t\tpes = append(pes, &processInfo{\n\t\t\tpid:  pe.ProcessID,\n\t\t\tppid: pe.ParentProcessID,\n\t\t\texe:  windows.UTF16ToString(pe.ExeFile[:]),\n\t\t})\n\t\treturn true\n\t})\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t} else if err = terminateProcess(ctx, cmd.Path, uint32(cmd.Process.Pid), sig, pes); err != nil {\n\t\tclog.Error(ctx, err)\n\t}\n}\n\n// terminateProcess will terminate the given process and all its children. The\n// children are terminated first.\nfunc terminateProcess(ctx context.Context, exe string, pid uint32, sig os.Signal, pes []*processInfo) error {\n\tif err := terminateChildrenOf(ctx, pid, sig, pes); err != nil {\n\t\treturn err\n\t}\n\n\tif sig == os.Interrupt {\n\t\tif err := windows.GenerateConsoleCtrlEvent(windows.CTRL_BREAK_EVENT, pid); err != nil {\n\t\t\t// An ACCESS_DENIED error may indicate that the process is dead already but\n\t\t\t// died just after the handle to it was opened.\n\t\t\tif errors.Is(err, windows.ERROR_ACCESS_DENIED) {\n\t\t\t\tif alive, aliveErr := processIsAlive(pid); aliveErr != nil {\n\t\t\t\t\tclog.Error(ctx, aliveErr)\n\t\t\t\t} else if !alive {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"%q: %w\", exe, &os.SyscallError{Syscall: \"GenerateConsoleCtrlEvent\", Err: err})\n\t\t}\n\t\tclog.Debugf(ctx, \"sent ctrl-c to process %q (pid %d)\", exe, pid)\n\t\treturn nil\n\t}\n\n\t// SYNCHRONIZE is required to wait for the process to terminate\n\th, err := windows.OpenProcess(windows.SYNCHRONIZE|windows.PROCESS_TERMINATE, true, pid)\n\tif err != nil {\n\t\tif errors.Is(err, windows.ERROR_INVALID_PARAMETER) {\n\t\t\t// ERROR_INVALID_PARAMETER means that the process no longer exists. It might\n\t\t\t// have died because we killed its children.\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"failed to open handle of %q: %w\", exe, err)\n\t}\n\tdefer func() {\n\t\t_ = windows.CloseHandle(h)\n\t}()\n\n\tif err = windows.TerminateProcess(h, 0); err != nil {\n\t\t// An ACCESS_DENIED error may indicate that the process is dead already but\n\t\t// died just after the handle to it was opened.\n\t\tif errors.Is(err, windows.ERROR_ACCESS_DENIED) {\n\t\t\tif alive, aliveErr := processIsAlive(pid); aliveErr != nil {\n\t\t\t\tclog.Error(ctx, aliveErr)\n\t\t\t} else if !alive {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn fmt.Errorf(\"%q: %w\", exe, &os.SyscallError{Syscall: \"TerminateProcess\", Err: err})\n\t}\n\tclog.Debugf(ctx, \"terminated process %q (pid %d)\", exe, pid)\n\treturn nil\n}\n\nfunc terminateChildrenOf(ctx context.Context, pid uint32, sig os.Signal, pes []*processInfo) error {\n\tfor _, pe := range pes {\n\t\tif pe.ppid == pid {\n\t\t\tif err := terminateProcess(ctx, pe.exe, pe.pid, sig, pes); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// processIsAlive checks if the given pid exists in the current process snapshot.\nfunc processIsAlive(pid uint32) (bool, error) {\n\tfound := false\n\terr := eachProcess(func(pe *windows.ProcessEntry32) bool {\n\t\tif pe.ProcessID == pid {\n\t\t\tfound = true\n\t\t\treturn false // break iteration\n\t\t}\n\t\treturn true\n\t})\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn found, nil\n}\n\n// eachProcess calls the given function with each ProcessEntry32 found\n// in the current process snapshot. The iteration ends if the given function\n// returns false.\nfunc eachProcess(f func(pe *windows.ProcessEntry32) bool) error {\n\th, err := windows.CreateToolhelp32Snapshot(windows.TH32CS_SNAPPROCESS, 0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to get process snapshot: %w\", err)\n\t}\n\tdefer func() {\n\t\t_ = windows.CloseHandle(h)\n\t}()\n\tpe := new(windows.ProcessEntry32)\n\tpe.Size = peSize\n\terr = windows.Process32First(h, pe)\n\tfor err == nil {\n\t\tif !f(pe) {\n\t\t\tbreak\n\t\t}\n\t\terr = windows.Process32Next(h, pe)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/proc/wsl_linux.go",
    "content": "//go:build linux\n\npackage proc\n\nimport (\n\t\"os\"\n\t\"strings\"\n)\n\nvar runningInWSL bool //nolint:gochecknoglobals // this is a constant\n\nfunc init() {\n\tpv, err := os.ReadFile(\"/proc/version\")\n\tif err == nil {\n\t\tsv := strings.ToLower(string(pv))\n\t\trunningInWSL = strings.Contains(sv, \"microsoft\") && strings.Contains(sv, \"wsl\")\n\t}\n}\n\n// RunningInWSL returns true if the current process is a Linux running under WSL.\nfunc RunningInWSL() bool {\n\treturn runningInWSL\n}\n"
  },
  {
    "path": "pkg/proc/wsl_other.go",
    "content": "//go:build !linux\n\npackage proc\n\n// RunningInWSL returns true if the current process is a Linux running under WSL.\nfunc RunningInWSL() bool {\n\treturn false\n}\n"
  },
  {
    "path": "pkg/restapi/api.go",
    "content": "package restapi\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/go-json-experiment/json\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/matcher\"\n)\n\nconst (\n\tHeaderCallerInterceptID = \"X-Telepresence-Caller-Intercept-Id\"\n\tHeaderInterceptID       = \"X-Telepresence-Intercept-Id\"\n\tEndPointConsumeHere     = \"/consume-here\"\n\tEndPointInterceptInfo   = \"/intercept-info\"\n)\n\ntype InterceptInfo struct {\n\t// True if the service is being intercepted\n\tIntercepted bool `json:\"intercepted\"`\n\n\t// True when queried on the workstation side, false if it is the cluster side agent.\n\tClientSide bool `json:\"clientSide\"`\n\n\t// Metadata associated with the intercept. Only available on when Intercepted == ClientSide\n\tMetadata map[string]string `json:\"metadata,omitempty\"`\n}\n\ntype AgentState interface {\n\t// InterceptInfo returns information about an ongoing intercept that matches\n\t// the given arguments.\n\tInterceptInfo(ctx context.Context, callerID, path string, containerPort uint16, headers http.Header) (*InterceptInfo, error)\n}\n\ntype Server interface {\n\tListenAndServe(context.Context, int) error\n\tServe(context.Context, net.Listener) error\n}\n\ntype ErrorResponse struct {\n\tError string `json:\"error,omitempty\"`\n}\n\nfunc NewServer(agent AgentState) Server {\n\treturn &server{\n\t\tagent: agent,\n\t}\n}\n\ntype server struct {\n\tagent AgentState\n}\n\n// ListenAndServe is like Serve but creates a TCP listener on \"localhost:<apiPort>\".\nfunc (s *server) ListenAndServe(c context.Context, apiPort int) error {\n\tln, err := net.Listen(\"tcp\", \":\"+strconv.Itoa(apiPort))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn s.Serve(c, ln)\n}\n\nfunc (s *server) interceptInfo(c context.Context, p string, cp uint16, h http.Header) (*InterceptInfo, error) {\n\tii, err := s.agent.InterceptInfo(c, h.Get(HeaderCallerInterceptID), p, cp, h)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclog.Debugf(c, \"InterceptInfo(path: %s, port %d, header %s => %v\", p, cp, matcher.HeaderStringer(h), ii)\n\treturn ii, nil\n}\n\n// Serve starts the API server. It terminates when the given context is done.\nfunc (s *server) Serve(c context.Context, ln net.Listener) error {\n\tmux := http.NewServeMux()\n\twriteError := func(w http.ResponseWriter, status int, err error) {\n\t\tw.WriteHeader(status)\n\t\tif err := json.MarshalWrite(w, &ErrorResponse{Error: err.Error()}); err != nil {\n\t\t\tclog.Errorf(c, \"error %v when responding with error %v\", err, err)\n\t\t}\n\t}\n\n\tcontainerPort := func(w http.ResponseWriter, r *http.Request) (uint16, bool) {\n\t\tif cpv := r.FormValue(\"containerPort\"); cpv != \"\" {\n\t\t\ti, err := strconv.ParseUint(cpv, 10, 16)\n\t\t\tif err != nil {\n\t\t\t\twriteError(w, http.StatusBadRequest, fmt.Errorf(\"containerPort: %w\", err))\n\t\t\t\treturn 0, false\n\t\t\t}\n\t\t\treturn uint16(i), true\n\t\t}\n\t\treturn 0, true\n\t}\n\n\tmux.HandleFunc(EndPointConsumeHere, func(w http.ResponseWriter, r *http.Request) {\n\t\tclog.Debugf(c, \"Received %s\", EndPointConsumeHere)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tcp, ok := containerPort(w, r)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tif ii, err := s.interceptInfo(c, r.FormValue(\"path\"), cp, r.Header); err != nil {\n\t\t\twriteError(w, http.StatusInternalServerError, err)\n\t\t} else {\n\t\t\t// Client must consume intercepted messages. Agent must not.\n\t\t\tconsumeHere := ii.Intercepted\n\t\t\tif !ii.ClientSide {\n\t\t\t\tconsumeHere = !consumeHere\n\t\t\t}\n\t\t\tif err = json.MarshalWrite(w, consumeHere); err != nil {\n\t\t\t\tclog.Errorf(c, \"error %v when responding with %t\", err, consumeHere)\n\t\t\t}\n\t\t}\n\t})\n\tmux.HandleFunc(EndPointInterceptInfo, func(w http.ResponseWriter, r *http.Request) {\n\t\tclog.Debugf(c, \"Received %s\", EndPointInterceptInfo)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tcp, ok := containerPort(w, r)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tif ii, err := s.interceptInfo(c, r.FormValue(\"path\"), cp, r.Header); err != nil {\n\t\t\twriteError(w, http.StatusInternalServerError, err)\n\t\t} else if err = json.MarshalWrite(w, &ii); err != nil {\n\t\t\tclog.Errorf(c, \"error %v when responding with %v\", err, ii)\n\t\t}\n\t})\n\tmux.HandleFunc(\"/healthz\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.WriteHeader(http.StatusOK)\n\t})\n\n\tsvc := http.Server{\n\t\tHandler:     mux,\n\t\tBaseContext: func(_ net.Listener) context.Context { return c },\n\t}\n\n\tinfo := fmt.Sprintf(\"Telepresence API server on %v\", ln.Addr())\n\tgo func() {\n\t\tclog.Infof(c, \"%s started\", info)\n\t\tdefer clog.Infof(c, \"%s ended\", info)\n\t\terr := svc.Serve(ln)\n\t\tif err != nil && !errors.Is(err, http.ErrServerClosed) {\n\t\t\tclog.Errorf(c, \"%s stopped. %v\", info, err)\n\t\t}\n\t}()\n\n\t<-c.Done()\n\tclog.Infof(c, \"Shutting down %s\", info)\n\tsc, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\terr := svc.Shutdown(sc)\n\tcancel()\n\treturn err\n}\n"
  },
  {
    "path": "pkg/restapi/api_test.go",
    "content": "package restapi_test\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/http\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/restapi\"\n)\n\ntype (\n\tyesNoClient  bool\n\tyesNoCluster bool\n)\n\nfunc (yn yesNoClient) InterceptInfo(_ context.Context, _, _ string, _ uint16, _ http.Header) (*restapi.InterceptInfo, error) {\n\treturn &restapi.InterceptInfo{Intercepted: bool(yn), ClientSide: true}, nil\n}\n\nfunc (yn yesNoCluster) InterceptInfo(_ context.Context, _, _ string, _ uint16, _ http.Header) (*restapi.InterceptInfo, error) {\n\treturn &restapi.InterceptInfo{Intercepted: bool(yn), ClientSide: false}, nil\n}\n\ntype (\n\ttextMatcher        map[string]string\n\ttextMatcherClient  textMatcher\n\ttextMatcherCluster textMatcher\n)\n\nfunc (t textMatcher) intercepted(header http.Header) bool {\n\tfor k, v := range t {\n\t\tif header.Get(k) != v {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (t textMatcherClient) InterceptInfo(_ context.Context, _, _ string, _ uint16, headers http.Header) (*restapi.InterceptInfo, error) {\n\treturn &restapi.InterceptInfo{Intercepted: textMatcher(t).intercepted(headers), ClientSide: true}, nil\n}\n\nfunc (t textMatcherCluster) InterceptInfo(_ context.Context, _, _ string, _ uint16, headers http.Header) (*restapi.InterceptInfo, error) {\n\treturn &restapi.InterceptInfo{Intercepted: textMatcher(t).intercepted(headers), ClientSide: false}, nil\n}\n\ntype matcherWithMetadata struct {\n\ttextMatcherCluster\n\tmeta map[string]string\n}\n\nfunc (t *matcherWithMetadata) InterceptInfo(ctx context.Context, callerID, path string, containerPort uint16, headers http.Header) (*restapi.InterceptInfo, error) {\n\tret, _ := t.textMatcherCluster.InterceptInfo(ctx, callerID, path, containerPort, headers)\n\tret.Metadata = t.meta\n\treturn ret, nil\n}\n\ntype callerIdMatcherClient string\n\nfunc (c callerIdMatcherClient) InterceptInfo(_ context.Context, callerID, _ string, _ uint16, _ http.Header) (*restapi.InterceptInfo, error) {\n\treturn &restapi.InterceptInfo{Intercepted: callerID == string(c), ClientSide: true}, nil\n}\n\ntype callerIdMatcherCluster string\n\nfunc (c callerIdMatcherCluster) InterceptInfo(_ context.Context, callerID, _ string, _ uint16, _ http.Header) (*restapi.InterceptInfo, error) {\n\treturn &restapi.InterceptInfo{Intercepted: callerID == string(c), ClientSide: false}, nil\n}\n\nfunc Test_server_intercepts(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\tagent    restapi.AgentState\n\t\theaders  map[string]string\n\t\tendpoint string\n\t\twant     any\n\t}{\n\t\t{\n\t\t\t\"client true\",\n\t\t\tyesNoClient(true),\n\t\t\tnil,\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"client false\",\n\t\t\tyesNoClient(false),\n\t\t\tnil,\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"cluster true\",\n\t\t\tyesNoCluster(true),\n\t\t\tnil,\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"cluster false\",\n\t\t\tyesNoCluster(false),\n\t\t\tnil,\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"client header - match\",\n\t\t\ttextMatcherClient{\n\t\t\t\trestapi.HeaderInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\tmap[string]string{\n\t\t\t\trestapi.HeaderInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"client header - no match\",\n\t\t\ttextMatcherClient{\n\t\t\t\trestapi.HeaderInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\tmap[string]string{\n\t\t\t\trestapi.HeaderInterceptID: \"xyz:123\",\n\t\t\t},\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"cluster header - match\",\n\t\t\ttextMatcherCluster{\n\t\t\t\trestapi.HeaderInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\tmap[string]string{\n\t\t\t\trestapi.HeaderInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"cluster header - match\",\n\t\t\t&matcherWithMetadata{\n\t\t\t\ttextMatcherCluster: textMatcherCluster{\n\t\t\t\t\trestapi.HeaderInterceptID: \"abc:123\",\n\t\t\t\t},\n\t\t\t\tmeta: map[string]string{\n\t\t\t\t\t\"a\": \"A\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tmap[string]string{\n\t\t\t\trestapi.HeaderInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\trestapi.EndPointInterceptInfo,\n\t\t\t&restapi.InterceptInfo{\n\t\t\t\tIntercepted: true,\n\t\t\t\tClientSide:  false,\n\t\t\t\tMetadata: map[string]string{\n\t\t\t\t\t\"a\": \"A\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"cluster header - no match\",\n\t\t\ttextMatcherCluster{\n\t\t\t\trestapi.HeaderInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\tmap[string]string{\n\t\t\t\trestapi.HeaderInterceptID: \"xyz:123\",\n\t\t\t},\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"client multi header - all matched\",\n\t\t\ttextMatcherClient{\n\t\t\t\t\"header-a\": \"value-a\",\n\t\t\t\t\"header-b\": \"value-b\",\n\t\t\t},\n\t\t\tmap[string]string{\n\t\t\t\t\"header-a\": \"value-a\",\n\t\t\t\t\"header-b\": \"value-b\",\n\t\t\t\t\"header-c\": \"value-c\",\n\t\t\t},\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"client multi header - not all matched\",\n\t\t\ttextMatcherClient{\n\t\t\t\t\"header-a\": \"value-a\",\n\t\t\t\t\"header-b\": \"value-b\",\n\t\t\t\t\"header-c\": \"value-c\",\n\t\t\t},\n\t\t\tmap[string]string{\n\t\t\t\t\"header-a\": \"value-a\",\n\t\t\t\t\"header-b\": \"value-b\",\n\t\t\t},\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\tfalse,\n\t\t},\n\t\t{\n\t\t\t\"client caller intercept id - match\",\n\t\t\tcallerIdMatcherClient(\"abc:123\"),\n\t\t\tmap[string]string{\n\t\t\t\trestapi.HeaderCallerInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\trestapi.EndPointConsumeHere,\n\t\t\ttrue,\n\t\t},\n\t\t{\n\t\t\t\"client caller intercept id - match\",\n\t\t\tcallerIdMatcherClient(\"abc:123\"),\n\t\t\tmap[string]string{\n\t\t\t\trestapi.HeaderCallerInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\trestapi.EndPointInterceptInfo,\n\t\t\t&restapi.InterceptInfo{\n\t\t\t\tIntercepted: true,\n\t\t\t\tClientSide:  true,\n\t\t\t\tMetadata:    nil,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\t\"cluster caller intercept id - match\",\n\t\t\tcallerIdMatcherCluster(\"abc:123\"),\n\t\t\tmap[string]string{\n\t\t\t\trestapi.HeaderCallerInterceptID: \"abc:123\",\n\t\t\t},\n\t\t\trestapi.EndPointInterceptInfo,\n\t\t\t&restapi.InterceptInfo{\n\t\t\t\tIntercepted: true,\n\t\t\t\tClientSide:  false,\n\t\t\t\tMetadata:    nil,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tc := testutil.NewContext(t, false)\n\t\t\tc, cancel := context.WithCancel(c)\n\t\t\tln, err := net.Listen(\"tcp\", \":0\")\n\t\t\trequire.NoError(t, err)\n\t\t\twg := sync.WaitGroup{}\n\t\t\twg.Add(1)\n\t\t\tgo func() {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tassert.NoError(t, restapi.NewServer(tt.agent).Serve(c, ln))\n\t\t\t}()\n\t\t\trq, err := http.NewRequest(http.MethodGet, \"http://\"+ln.Addr().String()+tt.endpoint, nil)\n\t\t\tfor k, v := range tt.headers {\n\t\t\t\trq.Header.Set(k, v)\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tr, err := http.DefaultClient.Do(rq)\n\t\t\trequire.NoError(t, err)\n\t\t\tdefer r.Body.Close()\n\t\t\tassert.Equal(t, r.StatusCode, http.StatusOK)\n\t\t\tif _, ok := tt.want.(bool); ok {\n\t\t\t\tvar rpl bool\n\t\t\t\trequire.NoError(t, json.UnmarshalRead(r.Body, &rpl))\n\t\t\t\tassert.Equal(t, tt.want, rpl)\n\t\t\t} else {\n\t\t\t\tvar rpl restapi.InterceptInfo\n\t\t\t\trequire.NoError(t, json.UnmarshalRead(r.Body, &rpl))\n\t\t\t\tassert.Equal(t, tt.want, &rpl)\n\t\t\t}\n\t\t\tcancel()\n\t\t\twg.Wait()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/routing/routing.go",
    "content": "package routing\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\ntype Route struct {\n\tInterfaceIndex int\n\tInterfaceName  string\n\tLocalIP        netip.Addr\n\tRoutedNet      netip.Prefix\n\tGateway        netip.Addr\n\tDefault        bool\n}\n\ntype Table interface {\n\t// Add adds a route to the routing table\n\tAdd(ctx context.Context, r *Route) error\n\t// Remove removes a route from the routing table\n\tRemove(ctx context.Context, r *Route) error\n\t// Close closes the routing table\n\tClose(ctx context.Context) error\n}\n\nfunc OpenTable(ctx context.Context) (Table, error) {\n\treturn openTable(ctx)\n}\n\nfunc DefaultRoute(ctx context.Context) (*Route, error) {\n\trt, err := GetRoutingTable(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, r := range rt {\n\t\tif r.Default {\n\t\t\treturn r, nil\n\t\t}\n\t}\n\treturn nil, errors.New(\"unable to find a default route\")\n}\n\n// NewRoute creates a new non-default route for the given prefix, interface index, and name.\nfunc NewRoute(rn netip.Prefix, ifIdx int, ifName string) Route {\n\tvar unSpec netip.Addr\n\tif rn.Addr().Is4() {\n\t\tunSpec = netip.IPv4Unspecified()\n\t} else {\n\t\tunSpec = netip.IPv6Unspecified()\n\t}\n\treturn Route{\n\t\tRoutedNet:      rn.Masked(),\n\t\tInterfaceIndex: ifIdx,\n\t\tInterfaceName:  ifName,\n\t\tLocalIP:        unSpec,\n\t\tGateway:        unSpec,\n\t}\n}\n\ntype rtError string\n\nfunc (r rtError) Error() string {\n\treturn string(r)\n}\n\nconst (\n\terrInconsistentRT      = rtError(\"routing table is inconsistent\")\n\tmaxInconsistentRetries = 3\n\tinconsistentRetryDelay = 50 * time.Millisecond\n)\n\n// GetRoutingTable will return a list of Route objects created from the current routing table.\nfunc GetRoutingTable(ctx context.Context) ([]*Route, error) {\n\t// The process of creating routes is not atomic. If an intercept is deleted shortly before this function is\n\t// called, then an interface referenced from a route might no longer exist. When this happens, there will\n\t// be a short delay followed by a retry.\n\tfor i := 0; i < maxInconsistentRetries; i++ {\n\t\trt, err := getConsistentRoutingTable(ctx)\n\t\tif err != errInconsistentRT {\n\t\t\treturn rt, err\n\t\t}\n\t\ttime.Sleep(inconsistentRetryDelay)\n\t}\n\treturn nil, errInconsistentRT\n}\n\nfunc (r *Route) Routes(ip netip.Addr) bool {\n\treturn r.RoutedNet.Contains(ip)\n}\n\nfunc (r *Route) String() string {\n\tbf := strings.Builder{}\n\tif !r.RoutedNet.Addr().IsUnspecified() {\n\t\tbf.WriteString(r.RoutedNet.String())\n\t\tbf.WriteByte(' ')\n\t}\n\tif r.LocalIP.IsValid() && !r.LocalIP.IsUnspecified() {\n\t\tbf.WriteString(\"via \")\n\t\tbf.WriteString(r.LocalIP.String())\n\t\tbf.WriteByte(' ')\n\t}\n\tbf.WriteString(\"dev \")\n\tbf.WriteString(r.InterfaceName)\n\tif !r.Gateway.IsUnspecified() {\n\t\tbf.WriteString(\" gw \")\n\t\tbf.WriteString(r.Gateway.String())\n\t}\n\tif r.Default {\n\t\tbf.WriteString(\" (default)\")\n\t}\n\treturn bf.String()\n}\n\n// AddStatic adds a specific route. This can be used to prevent certain IP addresses\n// from being routed to the route's interface.\nfunc (r *Route) AddStatic(ctx context.Context) (err error) {\n\tclog.Debugf(ctx, \"Adding static route %s\", r)\n\treturn r.addStatic(ctx)\n}\n\n// RemoveStatic removes a specific route added via AddStatic.\nfunc (r *Route) RemoveStatic(ctx context.Context) (err error) {\n\tclog.Debugf(ctx, \"Dropping static route %s\", r)\n\treturn r.removeStatic(ctx)\n}\n"
  },
  {
    "path": "pkg/routing/routing_darwin.go",
    "content": "package routing\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\n\t\"golang.org/x/net/route\"\n\t\"golang.org/x/sys/unix\"\n)\n\nconst (\n\tfindInterfaceRegex = \"(?:gateway:\\\\s+([0-9a-f:.]+)\\\\s+.*)?interface:\\\\s+([a-z0-9]+)\"\n\tdefaultRegex       = \"destination:\\\\s+default\"\n\tmaskRegex          = \"mask:\\\\s+([0-9a-f:.]+)\"\n)\n\nvar (\n\tfindInterfaceRe = regexp.MustCompile(findInterfaceRegex)\n\tdefaultRe       = regexp.MustCompile(defaultRegex)\n\tmaskRe          = regexp.MustCompile(maskRegex)\n)\n\nfunc getConsistentRoutingTable(ctx context.Context) ([]*Route, error) {\n\tb, err := route.FetchRIB(unix.AF_UNSPEC, route.RIBTypeRoute, 0)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tmsgs, err := route.ParseRIB(route.RIBTypeRoute, b)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\troutes := []*Route{}\n\tfor _, msg := range msgs {\n\t\trm := msg.(*route.RouteMessage)\n\t\tif rm.Flags&unix.RTF_UP == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tdst, gw, mask := rm.Addrs[unix.RTAX_DST], rm.Addrs[unix.RTAX_GATEWAY], rm.Addrs[unix.RTAX_NETMASK]\n\t\tif dst == nil || gw == nil || mask == nil {\n\t\t\tcontinue\n\t\t}\n\t\tiface, err := net.InterfaceByIndex(rm.Index)\n\t\tif err != nil {\n\t\t\t// This is not an atomic operation. An interface may vanish while we're iterating the RIB. When that\n\t\t\t// happens, the best cause of action is to redo the whole process.\n\t\t\treturn nil, errInconsistentRT\n\t\t}\n\t\tif iface.Flags&net.FlagUp == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tswitch a := dst.(type) {\n\t\tcase *route.Inet4Addr:\n\t\t\tlocalIP, err := interfaceLocalIP(iface, true)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tmask, ok := mask.(*route.Inet4Addr)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgwIP := netip.IPv4Unspecified()\n\t\t\tif gwAddr, ok := gw.(*route.Inet4Addr); ok {\n\t\t\t\tgwIP = netip.AddrFrom4(gwAddr.IP)\n\t\t\t}\n\t\t\tip4Mask := net.IPv4Mask(mask.IP[0], mask.IP[1], mask.IP[2], mask.IP[3])\n\t\t\tones, _ := ip4Mask.Size()\n\t\t\troutedNet := netip.PrefixFrom(netip.AddrFrom4(a.IP), ones)\n\t\t\troutes = append(routes, &Route{\n\t\t\t\tInterfaceIndex: iface.Index,\n\t\t\t\tInterfaceName:  iface.Name,\n\t\t\t\tGateway:        gwIP,\n\t\t\t\tLocalIP:        localIP,\n\t\t\t\tRoutedNet:      routedNet,\n\t\t\t\tDefault:        ones == 0,\n\t\t\t})\n\t\tcase *route.Inet6Addr:\n\t\t\tlocalIP, err := interfaceLocalIP(iface, false)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tmask, ok := mask.(*route.Inet6Addr)\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbits, _ := net.IPMask(mask.IP[:]).Size()\n\t\t\troutedNet := netip.PrefixFrom(netip.AddrFrom16(a.IP), bits)\n\t\t\tgwIP := netip.IPv6Unspecified()\n\t\t\tif gwAddr, ok := gw.(*route.Inet6Addr); ok {\n\t\t\t\tgwIP = netip.AddrFrom16(gwAddr.IP)\n\t\t\t}\n\t\t\troutes = append(routes, &Route{\n\t\t\t\tInterfaceIndex: iface.Index,\n\t\t\t\tInterfaceName:  iface.Name,\n\t\t\t\tGateway:        gwIP,\n\t\t\t\tLocalIP:        localIP,\n\t\t\t\tRoutedNet:      routedNet,\n\t\t\t\tDefault:        bits == 0,\n\t\t\t})\n\t\t}\n\t}\n\treturn routes, nil\n}\n\nfunc getOsRoute(ctx context.Context, routedNet netip.Prefix) (*Route, error) {\n\tip := routedNet.Addr()\n\targs := []string{\"-n\", \"get\"}\n\tif ip.Is6() {\n\t\targs = append(args, \"-inet6\")\n\t}\n\targs = append(args, ip.String())\n\tcmd := exec.CommandContext(ctx, \"route\", args...)\n\terrOut := bytes.Buffer{}\n\tcmd.Stderr = &errOut\n\tout, err := cmd.Output()\n\tif err == nil && len(out) == 0 {\n\t\terr = errors.New(errOut.String())\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to run 'route %s': %w\", args, err)\n\t}\n\tmatch := findInterfaceRe.FindStringSubmatch(string(out))\n\t// This might fail because no \"gateway\" is listed. The problem is that without a gateway IP we can't\n\t// route to the network anyway, so we should just return an error.\n\tif match == nil {\n\t\treturn nil, fmt.Errorf(\"%s did not match output of %s:\\n%q\", findInterfaceRegex, cmd, out)\n\t}\n\tifaceName := match[2]\n\tiface, err := net.InterfaceByName(ifaceName)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to get interface object for interface %s: %w\", ifaceName, err)\n\t}\n\tvar gatewayIp netip.Addr\n\tif gateway := match[1]; gateway != \"\" {\n\t\tgatewayIp, err = netip.ParseAddr(gateway)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to parse gateway %s: %v\", gateway, err)\n\t\t}\n\t} else if ip.Is4() {\n\t\tgatewayIp = netip.IPv4Unspecified()\n\t} else {\n\t\tgatewayIp = netip.IPv6Unspecified()\n\t}\n\tlocalIP, err := interfaceLocalIP(iface, ip.Is4())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tones := routedNet.Bits()\n\tif match := maskRe.FindStringSubmatch(string(out)); match != nil {\n\t\tif addr, err := netip.ParseAddr(match[1]); err == nil {\n\t\t\tif addr.Is4In6() {\n\t\t\t\taddr = netip.AddrFrom4(addr.As4())\n\t\t\t}\n\t\t\tmask := net.IPMask(addr.AsSlice())\n\t\t\tones, _ = mask.Size()\n\t\t}\n\t}\n\trouted := netip.PrefixFrom(ip, ones)\n\tisDefault := false\n\tif match := defaultRe.FindStringSubmatch(string(out)); match != nil {\n\t\tisDefault = true\n\t}\n\tisDefault = isDefault || ones == 0\n\treturn &Route{\n\t\tRoutedNet:      routed,\n\t\tLocalIP:        localIP,\n\t\tInterfaceIndex: iface.Index,\n\t\tInterfaceName:  iface.Name,\n\t\tGateway:        gatewayIp,\n\t\tDefault:        isDefault,\n\t}, nil\n}\n\n// withRouteSocket will open the socket to where RouteMessages should be sent\n// and call the given function with that socket. The socket is closed when the\n// function returns.\nfunc withRouteSocket(f func(routeSocket int) error) error {\n\trouteSocket, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, unix.AF_UNSPEC)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Avoid the overhead of echoing messages back to sender\n\tif err = unix.SetsockoptInt(routeSocket, unix.SOL_SOCKET, unix.SO_USELOOPBACK, 0); err != nil {\n\t\treturn err\n\t}\n\tdefer unix.Close(routeSocket)\n\treturn f(routeSocket)\n}\n\n// toRouteAddr converts a net.IP to its corresponding addrMessage.Addr.\nfunc toRouteAddr(ip netip.Addr) (addr route.Addr) {\n\tif ip.Is4() || ip.Is4In6() {\n\t\treturn &route.Inet4Addr{IP: ip.As4()}\n\t}\n\treturn &route.Inet6Addr{IP: ip.As16()}\n}\n\nfunc toRoute4Mask(bits int) (addr route.Addr) {\n\tmask := net.CIDRMask(bits, 32)\n\tdst := route.Inet4Addr{}\n\tcopy(dst.IP[:], mask)\n\treturn &dst\n}\n\nfunc toRoute6Mask(bits int) (addr route.Addr) {\n\tmask := net.CIDRMask(bits, 128)\n\tdst := route.Inet6Addr{}\n\tcopy(dst.IP[:], mask)\n\treturn &dst\n}\n\nfunc newRouteMessage(rtm, seq int, subnet netip.Prefix, gw netip.Addr) *route.RouteMessage {\n\tvar mask route.Addr\n\tif subnet.Addr().Is4() {\n\t\tmask = toRoute4Mask(subnet.Bits())\n\t\tif !gw.IsValid() {\n\t\t\tgw = netip.IPv4Unspecified()\n\t\t}\n\t} else {\n\t\tmask = toRoute6Mask(subnet.Bits())\n\t\tif !gw.IsValid() {\n\t\t\tgw = netip.IPv6Unspecified()\n\t\t}\n\t}\n\tflags := unix.RTF_UP | unix.RTF_STATIC | unix.RTF_CLONING\n\tif !gw.IsUnspecified() {\n\t\tflags |= unix.RTF_GATEWAY\n\t}\n\treturn &route.RouteMessage{\n\t\tVersion: unix.RTM_VERSION,\n\t\tID:      uintptr(os.Getpid()),\n\t\tSeq:     seq,\n\t\tType:    rtm,\n\t\tFlags:   flags,\n\t\tAddrs: []route.Addr{\n\t\t\tunix.RTAX_DST:     toRouteAddr(subnet.Addr()),\n\t\t\tunix.RTAX_GATEWAY: toRouteAddr(gw),\n\t\t\tunix.RTAX_NETMASK: mask,\n\t\t},\n\t}\n}\n\nfunc Add(seq int, r netip.Prefix, gw netip.Addr) error {\n\treturn withRouteSocket(func(routeSocket int) error {\n\t\tm := newRouteMessage(unix.RTM_ADD, seq, r, gw)\n\t\twb, err := m.Marshal()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = unix.Write(routeSocket, wb)\n\t\tif err == unix.EEXIST {\n\t\t\t// route exists, that's OK\n\t\t\terr = nil\n\t\t}\n\t\treturn err\n\t})\n}\n\nfunc Clear(seq int, r netip.Prefix, gw netip.Addr) error {\n\treturn withRouteSocket(func(routeSocket int) error {\n\t\tm := newRouteMessage(unix.RTM_DELETE, seq, r, gw)\n\t\twb, err := m.Marshal()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = unix.Write(routeSocket, wb)\n\t\tif err == unix.ESRCH {\n\t\t\t// addrMessage doesn't exist, that's OK\n\t\t\terr = nil\n\t\t}\n\t\treturn err\n\t})\n}\n\nfunc (r *Route) addStatic(ctx context.Context) error {\n\treturn Add(1, r.RoutedNet, r.Gateway)\n}\n\nfunc (r *Route) removeStatic(ctx context.Context) error {\n\treturn Clear(1, r.RoutedNet, r.Gateway)\n}\n\ntype table struct{}\n\nfunc openTable(ctx context.Context) (Table, error) {\n\treturn &table{}, nil\n}\n\nfunc (t *table) Close(ctx context.Context) error {\n\treturn nil\n}\n\nfunc (t *table) Add(ctx context.Context, r *Route) error {\n\treturn r.AddStatic(ctx)\n}\n\nfunc (t *table) Remove(ctx context.Context, r *Route) error {\n\treturn r.RemoveStatic(ctx)\n}\n\nfunc osCompareRoutes(ctx context.Context, osRoute, tableRoute *Route) (bool, error) {\n\treturn false, nil\n}\n\nfunc interfaceLocalIP(iface *net.Interface, ipv4 bool) (netip.Addr, error) {\n\tias, err := iface.Addrs()\n\tif err != nil {\n\t\treturn netip.Addr{}, fmt.Errorf(\"unable to get interface addresses for interface %s: %w\", iface.Name, err)\n\t}\n\tfor _, ia := range ias {\n\t\tpfx, err := netip.ParsePrefix(ia.String())\n\t\tif err != nil {\n\t\t\treturn netip.Addr{}, fmt.Errorf(\"unable to parse address %s: %v\", ia.String(), err)\n\t\t}\n\t\tip := pfx.Addr()\n\t\tif ip.Is4() {\n\t\t\tif !ipv4 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn ip, nil\n\t\t} else if ipv4 {\n\t\t\tcontinue\n\t\t}\n\t\treturn ip, nil\n\t}\n\tif ipv4 {\n\t\treturn netip.IPv4Unspecified(), nil\n\t}\n\treturn netip.IPv6Unspecified(), nil\n}\n"
  },
  {
    "path": "pkg/routing/routing_linux.go",
    "content": "package routing\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os/exec\"\n\t\"sort\"\n\t\"syscall\" //nolint:depguard // sys/unix does not have NetlinkRIB\n\n\t\"github.com/vishvananda/netlink\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/subnet\"\n)\n\ntype LinuxTable interface {\n\tRouteToNetlink(route *Route) *netlink.Route\n}\n\ntype table struct {\n\tindex int\n\trule  *netlink.Rule\n}\n\nfunc getConsistentRoutingTable(ctx context.Context) ([]*Route, error) {\n\t// List routes in the all tables.\n\trts, err := netlink.RouteListFiltered(\n\t\tnetlink.FAMILY_ALL,\n\t\t&netlink.Route{},\n\t\tnetlink.RT_FILTER_TABLE,\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"netlink.RouteListFiltered: %w\", err)\n\t}\n\n\tvar routes []*Route\n\tfor i := range rts {\n\t\tnrt := &rts[i]\n\t\tif nrt.Table > 255 {\n\t\t\t// We only care about routes in the \"local\", \"main\", and \"default\" tables\n\t\t\tcontinue\n\t\t}\n\t\tswitch nrt.Family {\n\t\tcase syscall.AF_INET, syscall.AF_INET6:\n\t\t\trt, err := routeFromNetlinkRoute(nrt)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errInconsistentRT\n\t\t\t}\n\t\t\tclog.Tracef(ctx, \"Found route %s\", rt)\n\t\t\troutes = append(routes, rt)\n\t\t}\n\t}\n\treturn routes, nil\n}\n\nfunc routeFromNetlinkRoute(rt *netlink.Route) (*Route, error) {\n\tlnk, err := netlink.LinkByIndex(rt.LinkIndex)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"netlink.LinkByIndex: %w\", err)\n\t}\n\taddr, _ := netip.AddrFromSlice(rt.Src)\n\tgw, _ := netip.AddrFromSlice(rt.Gw)\n\tdst := iputil.PrefixFromIPNet(rt.Dst)\n\tdfltGw := gw.IsValid() && dst.Addr().IsUnspecified()\n\treturn &Route{\n\t\tInterfaceIndex: rt.LinkIndex,\n\t\tInterfaceName:  lnk.Attrs().Name,\n\t\tLocalIP:        addr,\n\t\tRoutedNet:      iputil.PrefixFromIPNet(rt.Dst),\n\t\tGateway:        gw,\n\t\tDefault:        dfltGw,\n\t}, nil\n}\n\nfunc getOsRoute(ctx context.Context, routedNet netip.Prefix) (*Route, error) {\n\tnrt, err := netlink.RouteGet(routedNet.Addr().AsSlice())\n\tif err == nil && len(nrt) > 0 {\n\t\treturn routeFromNetlinkRoute(&nrt[0])\n\t}\n\treturn nil, err\n}\n\nfunc openTable(ctx context.Context) (Table, error) {\n\trules, err := netlink.RuleList(netlink.FAMILY_ALL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"netlink.RuleList: %w\", err)\n\t}\n\t// Sort the rules by index ascending to make sure we find an open one\n\tsort.Slice(rules, func(i, j int) bool {\n\t\treturn rules[i].Table < rules[j].Table\n\t})\n\tindex := 775\n\tpriority := 32766 // default initial priority\n\tfor _, rule := range rules {\n\t\tif rule.Table == 0 || rule.Table == 255 {\n\t\t\t// System rules, ignore\n\t\t\tcontinue\n\t\t}\n\t\tif rule.Priority <= priority {\n\t\t\tpriority = rule.Priority - 1\n\t\t}\n\t\tif rule.Table == index {\n\t\t\t// There's already a table with the default index, get a new one\n\t\t\tindex++\n\t\t}\n\t}\n\tclog.Infof(ctx, \"Creating routing table with index %d and priority %d\", index, priority)\n\trule := netlink.NewRule()\n\trule.Table = index\n\trule.Priority = priority\n\trule.Family = netlink.FAMILY_V4\n\tif err := netlink.RuleAdd(rule); err != nil {\n\t\treturn nil, fmt.Errorf(\"netlink.RuleAdd: %w\", err)\n\t}\n\treturn &table{\n\t\tindex: index,\n\t\trule:  rule,\n\t}, nil\n}\n\nfunc (t *table) RouteToNetlink(route *Route) *netlink.Route {\n\trn := route.RoutedNet\n\treturn &netlink.Route{\n\t\tDst:       subnet.PrefixToIPNet(rn),\n\t\tTable:     t.index,\n\t\tLinkIndex: route.InterfaceIndex,\n\t\tGw:        route.Gateway.AsSlice(),\n\t\tSrc:       route.LocalIP.AsSlice(),\n\t}\n}\n\nfunc (t *table) Close(ctx context.Context) error {\n\treturn netlink.RuleDel(t.rule)\n}\n\nfunc (t *table) Add(ctx context.Context, r *Route) error {\n\troute := t.RouteToNetlink(r)\n\tif err := netlink.RouteAdd(route); err != nil {\n\t\treturn fmt.Errorf(\"netlink.RouteAdd: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (t *table) Remove(ctx context.Context, r *Route) error {\n\troute := t.RouteToNetlink(r)\n\tif err := netlink.RouteDel(route); err != nil {\n\t\treturn fmt.Errorf(\"netlink.RouteDel: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (r *Route) addStatic(ctx context.Context) error {\n\treturn exec.CommandContext(ctx, \"ip\", \"route\", \"add\", r.RoutedNet.String(), \"via\", r.Gateway.String(), \"dev\", r.InterfaceName).Run()\n}\n\nfunc (r *Route) removeStatic(ctx context.Context) error {\n\treturn exec.CommandContext(ctx, \"ip\", \"route\", \"del\", r.RoutedNet.String(), \"via\", r.Gateway.String(), \"dev\", r.InterfaceName).Run()\n}\n\nfunc osCompareRoutes(ctx context.Context, osRoute, tableRoute *Route) (bool, error) {\n\t// On Linux, when we ask about an IP address assigned to the machine, the OS will give us a loopback route\n\tif osRoute.LocalIP == osRoute.RoutedNet.Addr() {\n\t\tosIf, err := net.InterfaceByIndex(osRoute.InterfaceIndex)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t\tif osIf.Flags&net.FlagLoopback != 0 {\n\t\t\ttbIf, err := net.InterfaceByIndex(tableRoute.InterfaceIndex)\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\taddrs, err := tbIf.Addrs()\n\t\t\tif err != nil {\n\t\t\t\treturn false, err\n\t\t\t}\n\t\t\tfor _, addr := range addrs {\n\t\t\t\tclog.Tracef(ctx, \"Checking address %s against %s\", addr, osRoute.RoutedNet.Addr())\n\t\t\t\tif a, ok := netip.AddrFromSlice(iputil.Normalize(addr.(*net.IPNet).IP)); ok && a == osRoute.LocalIP {\n\t\t\t\t\treturn true, nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn false, nil\n}\n"
  },
  {
    "path": "pkg/routing/routing_test.go",
    "content": "package routing\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/telepresenceio/clog/testutil\"\n)\n\nfunc TestGetRoutingTable_defaultRoute(t *testing.T) {\n\tctx := testutil.NewContext(t, true)\n\trt, err := GetRoutingTable(ctx)\n\tassert.NoError(t, err)\n\tvar dflt *Route\n\tfor _, r := range rt {\n\t\tif r.Default {\n\t\t\tdflt = r\n\t\t\tbreak\n\t\t}\n\t}\n\tassert.NotNil(t, dflt)\n\tassert.NotEqual(t, netip.IPv4Unspecified(), dflt.Gateway)\n\tassert.NotEqual(t, netip.IPv6Unspecified(), dflt.Gateway)\n}\n\nfunc TestGetRoutingTable(t *testing.T) {\n\tctx := testutil.NewContext(t, true)\n\trt, err := GetRoutingTable(ctx)\n\tassert.NoError(t, err)\n\tassert.NotEmpty(t, rt)\n\tfor _, r := range rt {\n\t\tt.Logf(\"Route: %s\", r)\n\t\tassert.False(t, r.LocalIP.IsUnspecified())\n\t\tassert.NotZero(t, r.InterfaceIndex)\n\t\tassert.True(t, r.Default || !r.RoutedNet.Addr().IsUnspecified())\n\t}\n}\n"
  },
  {
    "path": "pkg/routing/routing_unix.go",
    "content": "//go:build !windows\n\npackage routing\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\nfunc GetRoute(ctx context.Context, routedNet netip.Prefix) (*Route, error) {\n\t// This is a two-step process. First, the OS will be queried for the route directly.\n\t// Whatever it gives back is not necessarily going to look exactly like the route from the routing table.\n\t// i.e., the OS will not tell us, for example, if this is the default route.\n\t// So once we have the route from the OS, we'll query the routing table to get the full route.\n\t// If this seems a little contrived, it's because it is. But there are NO system calls that directly query an OS for\n\t// a route's corresponding entry in the table, and we shouldn't re-implement the OS' routing logic itself by\n\t// trying to route the packet analytically from the table.\n\tosRoute, err := getOsRoute(ctx, routedNet)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trt, err := GetRoutingTable(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar defaultRoute *Route\n\tfor _, r := range rt {\n\t\tif ok, err := compareRoutes(ctx, osRoute, r); ok && err == nil {\n\t\t\tif r.Routes(routedNet.Addr()) {\n\t\t\t\treturn r, nil\n\t\t\t} else if r.Default {\n\t\t\t\tdefaultRoute = r\n\t\t\t}\n\t\t} else if err != nil {\n\t\t\tclog.Errorf(ctx, \"Unable to compare routes %s and %s: %v\", r, osRoute, err)\n\t\t}\n\t}\n\tif defaultRoute != nil {\n\t\tclog.Tracef(ctx, \"Picked default route %s for network %s\", defaultRoute, routedNet)\n\t\treturn defaultRoute, nil\n\t}\n\treturn nil, fmt.Errorf(\"unable to find route for %s\", routedNet)\n}\n\nfunc compareRoutes(ctx context.Context, osRoute, tableRoute *Route) (bool, error) {\n\tclog.Tracef(ctx, \"Comparing OS route %q to table route %q\", osRoute, tableRoute)\n\tif osRoute.InterfaceIndex == tableRoute.InterfaceIndex {\n\t\treturn true, nil\n\t}\n\treturn osCompareRoutes(ctx, osRoute, tableRoute)\n}\n"
  },
  {
    "path": "pkg/routing/routing_unix_test.go",
    "content": "//go:build !windows\n\npackage routing\n\nimport (\n\t\"net\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n)\n\nfunc TestGetRouteConsistency(t *testing.T) {\n\tctx := testutil.NewContext(t, true)\n\taddresses := map[string]struct{}{\n\t\t\"192.168.1.23\": {},\n\t\t\"10.0.5.3\":     {},\n\t\t\"127.0.0.1\":    {},\n\t\t\"8.8.8.8\":      {},\n\t}\n\ttable, err := GetRoutingTable(ctx)\n\tassert.NoError(t, err)\n\tfor _, route := range table {\n\t\tif ip := route.RoutedNet.Addr(); ip.Is4() {\n\t\t\tif ip.IsUnspecified() || ip.IsMulticast() {\n\t\t\t\t// Don't test 0.0.0.0 or any multicast addresses.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tclog.Debugf(ctx, \"Adding route %s\", route)\n\t\t\taddresses[ip.String()] = struct{}{}\n\t\t\tif route.RoutedNet.Bits() < 32 {\n\t\t\t\tip2 := ip.As4()\n\t\t\t\tip2[3] += 2\n\t\t\t\ta := netip.AddrFrom4(ip2)\n\t\t\t\taddresses[a.String()] = struct{}{}\n\t\t\t\tclog.Debugf(ctx, \"Adding IP %s\", a)\n\t\t\t}\n\t\t}\n\t}\n\tfor addr := range addresses {\n\t\tt.Run(addr, func(t *testing.T) {\n\t\t\ttestNet := netip.PrefixFrom(netip.MustParseAddr(addr), 32)\n\t\t\tosRoute, err := getOsRoute(ctx, testNet)\n\t\t\trequire.NoError(t, err)\n\t\t\troute, err := GetRoute(ctx, testNet)\n\t\t\trequire.NoError(t, err)\n\t\t\tosRouteIf, err := net.InterfaceByIndex(osRoute.InterfaceIndex)\n\t\t\trequire.NoError(t, err)\n\n\t\t\t// This is about as much as we can actually assert, because OSs tend to create\n\t\t\t// routes on the fly when, for example, a default route is hit. So there's no guarantee\n\t\t\t// that the matching \"original\" route in the table will be identical to the route returned on the fly.\n\t\t\tif runtime.GOOS == \"linux\" && osRouteIf.Flags&net.FlagLoopback != 0 && osRoute.LocalIP == osRoute.RoutedNet.Addr() {\n\t\t\t\trouteIf, err := net.InterfaceByIndex(route.InterfaceIndex)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\taddrs, err := routeIf.Addrs()\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.True(t, func() bool {\n\t\t\t\t\tfor _, addr := range addrs {\n\t\t\t\t\t\ta, ok := netip.AddrFromSlice(iputil.Normalize(addr.(*net.IPNet).IP))\n\t\t\t\t\t\tif ok && a == osRoute.LocalIP {\n\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn false\n\t\t\t\t}(), \"Interface addresses %v don't include route's local IP %s\", addrs, osRoute.LocalIP)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, osRoute.InterfaceIndex, route.InterfaceIndex, \"Routes %s and %s differ\", osRoute, route)\n\t\t\t}\n\t\t\trequire.True(t, route.RoutedNet.Contains(osRoute.RoutedNet.Addr()) || route.Default, \"Route %s doesn't route requested IP %s\", route, osRoute.RoutedNet.Addr())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/routing/routing_windows.go",
    "content": "package routing\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"golang.org/x/sys/windows\"\n\t\"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\ntype table struct{}\n\nfunc rowAsRoute(row *winipcfg.MibIPforwardRow2, localIP netip.Addr) (*Route, error) {\n\tdst := row.DestinationPrefix.Prefix()\n\tif !dst.IsValid() {\n\t\treturn nil, nil\n\t}\n\tgw := row.NextHop.Addr()\n\tif !gw.IsValid() {\n\t\treturn nil, nil\n\t}\n\tifaceIdx := int(row.InterfaceIndex)\n\tiface, err := net.InterfaceByIndex(ifaceIdx)\n\tif err != nil {\n\t\treturn nil, errInconsistentRT\n\t}\n\treturn &Route{\n\t\tLocalIP:        localIP,\n\t\tGateway:        gw,\n\t\tRoutedNet:      dst,\n\t\tInterfaceIndex: iface.Index,\n\t\tInterfaceName:  iface.Name,\n\t\tDefault:        dst.Addr().IsUnspecified(),\n\t}, nil\n}\n\nfunc getConsistentRoutingTable(ctx context.Context) ([]*Route, error) {\n\ttable, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to get routing table: %w\", err)\n\t}\n\troutes := make([]*Route, 0, len(table))\n\tfor _, row := range table {\n\t\tr, err := rowAsRoute(&row, netip.Addr{})\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif r != nil {\n\t\t\troutes = append(routes, r)\n\t\t}\n\t}\n\treturn routes, nil\n}\n\nfunc getRouteForIP(localIP netip.Addr) (*Route, error) {\nretryInconsistent:\n\tfor i := 0; i < maxInconsistentRetries; i++ {\n\t\ttable, err := winipcfg.GetIPForwardTable2(windows.AF_UNSPEC)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"unable to get routing table: %w\", err)\n\t\t}\n\t\tfor _, row := range table {\n\t\t\tifaceIdx := int(row.InterfaceIndex)\n\t\t\tif iface, err := net.InterfaceByIndex(ifaceIdx); err == nil && iface.Flags&net.FlagUp == net.FlagUp {\n\t\t\t\tif addrs, err := iface.Addrs(); err == nil {\n\t\t\t\t\tfor _, addr := range addrs {\n\t\t\t\t\t\tif pfx, err := netip.ParsePrefix(addr.String()); err == nil && pfx.Addr() == localIP {\n\t\t\t\t\t\t\tr, err := rowAsRoute(&row, pfx.Addr())\n\t\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\t\tif err == errInconsistentRT {\n\t\t\t\t\t\t\t\t\ttime.Sleep(inconsistentRetryDelay)\n\t\t\t\t\t\t\t\t\tcontinue retryInconsistent\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif r != nil {\n\t\t\t\t\t\t\t\treturn r, nil\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbreak\n\t}\n\treturn nil, fmt.Errorf(\"unable to get interface index for IP %s\", localIP.String())\n}\n\nfunc GetRoute(ctx context.Context, routedNet netip.Prefix) (*Route, error) {\n\tctx, cancel := context.WithTimeout(ctx, 2*time.Second)\n\tdefer cancel()\n\tip := routedNet.Addr()\n\tcmd := proc.CommandContext(ctx, \"pathping\", \"-n\", \"-h\", \"1\", \"-p\", \"100\", \"-w\", \"100\", \"-q\", \"1\", ip.String())\n\tstderr := &strings.Builder{}\n\tcmd.Stderr = stderr\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to run 'pathping %s': %s (%w)\", ip, stderr, err)\n\t}\n\tscanner := bufio.NewScanner(bytes.NewReader(out))\n\tipLine := regexp.MustCompile(`^\\s+0\\s+(\\S+)\\s*$`)\n\tfor scanner.Scan() {\n\t\tif match := ipLine.FindStringSubmatch(scanner.Text()); match != nil {\n\t\t\tif localIP, err := netip.ParseAddr(match[1]); err == nil {\n\t\t\t\treturn getRouteForIP(localIP)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil, fmt.Errorf(\"unable to parse local IP from %q\", string(out))\n}\n\nfunc maskToIP(mask net.IPMask) (ip net.IP) {\n\tip = make(net.IP, len(mask))\n\tcopy(ip[:], mask)\n\treturn ip\n}\n\nfunc (r *Route) addStatic(ctx context.Context) error {\n\tip := r.RoutedNet.Addr()\n\tvar maskSize int\n\tif ip.Is4() {\n\t\tmaskSize = 32\n\t} else {\n\t\tmaskSize = 128\n\t}\n\targs := []string{\n\t\t\"ADD\",\n\t\tip.String(),\n\t}\n\tif r.RoutedNet.Bits() < maskSize {\n\t\tmask := net.CIDRMask(r.RoutedNet.Bits(), maskSize)\n\t\targs = append(args, \"MASK\", maskToIP(mask).String())\n\t}\n\n\t// Contrary to what the usage printout says, he gateway must be appended even if it is unspecified. If it is missing,\n\t// the command just prints its usage and exits with code -1.\n\targs = append(args, r.Gateway.String())\n\n\targs = append(args, \"IF\", strconv.Itoa(r.InterfaceIndex))\n\tcmd := proc.CommandContext(ctx, \"route\", args...)\n\tout, err := cmd.Output()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create route %s: %w\", r, err)\n\t}\n\tif !strings.Contains(string(out), \"OK!\") {\n\t\treturn fmt.Errorf(\"failed to create route %s: %s\", r, strings.TrimSpace(string(out)))\n\t}\n\treturn nil\n}\n\nfunc (r *Route) removeStatic(ctx context.Context) error {\n\tcmd := proc.CommandContext(ctx,\n\t\t\"route\",\n\t\t\"DELETE\",\n\t\tr.RoutedNet.Addr().String(),\n\t)\n\terr := cmd.Run()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to delete route %s: %w\", r, err)\n\t}\n\treturn nil\n}\n\nfunc openTable(ctx context.Context) (Table, error) {\n\treturn &table{}, nil\n}\n\nfunc (t *table) Add(ctx context.Context, r *Route) error {\n\treturn r.AddStatic(ctx)\n}\n\nfunc (t *table) Remove(ctx context.Context, r *Route) error {\n\treturn r.RemoveStatic(ctx)\n}\n\nfunc (t *table) Close(ctx context.Context) error {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/shellquote/shellstring.go",
    "content": "package shellquote\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\ntype stringer struct {\n\texe  string\n\targs []string\n}\n\nfunc (s stringer) String() string {\n\tb := strings.Builder{}\n\tb.WriteString(quoteArg(s.exe))\n\tfor _, a := range s.args {\n\t\tb.WriteByte(' ')\n\t\tb.WriteString(quoteArg(a))\n\t}\n\treturn b.String()\n}\n\nfunc ShellString(exe string, args []string) fmt.Stringer {\n\treturn stringer{exe: exe, args: args}\n}\n\nvar UnixEscape = regexp.MustCompile(`[^\\w!%+,\\-./:=@^]`)\n\n// Unix checks if the give string contains characters that have special meaning for a\n// shell. If it does, it will be quoted using single quotes. If the string itself contains\n// single quotes, then the string is split on single quotes, each single quote is escaped\n// and each segment between the escaped single quotes is quoted separately.\nfunc Unix(arg string) string {\n\tif arg == \"\" {\n\t\treturn `''`\n\t}\n\tif !UnixEscape.MatchString(arg) {\n\t\treturn arg\n\t}\n\n\tb := strings.Builder{}\n\tqp := strings.IndexByte(arg, '\\'')\n\tif qp < 0 {\n\t\tb.WriteByte('\\'')\n\t\tb.WriteString(arg)\n\t\tb.WriteByte('\\'')\n\t} else {\n\t\tfor {\n\t\t\tif qp > 0 {\n\t\t\t\t// Write quoted string up to qp\n\t\t\t\tb.WriteString(Unix(arg[:qp]))\n\t\t\t}\n\t\t\tb.WriteString(`\\'`)\n\t\t\tqp++\n\t\t\tif qp >= len(arg) {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\targ = arg[qp:]\n\t\t\tif qp = strings.IndexByte(arg, '\\''); qp < 0 {\n\t\t\t\tif len(arg) > 0 {\n\t\t\t\t\tb.WriteString(Unix(arg))\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn b.String()\n}\n\nfunc Windows(arg string) string {\n\tif arg == \"\" {\n\t\treturn `\"\"`\n\t}\n\tneedsBackslash := false\n\tneedsQuote := false\n\tfor _, c := range arg {\n\t\tswitch c {\n\t\tcase '\"', '\\\\':\n\t\t\tneedsBackslash = true\n\t\tcase ' ', '\\t':\n\t\t\tneedsQuote = true\n\t\t}\n\t}\n\tif !(needsBackslash || needsQuote) {\n\t\treturn arg\n\t}\n\n\tb := strings.Builder{}\n\tslashes := 0\n\tslashOut := func() {\n\t\tfor ; slashes > 0; slashes-- {\n\t\t\tb.WriteByte('\\\\')\n\t\t}\n\t}\n\tslashOutBeforeQuote := func(escape bool) {\n\t\tslashes <<= 1\n\t\tif escape {\n\t\t\tslashes++\n\t\t}\n\t\tslashOut()\n\t}\n\n\tif needsQuote {\n\t\tb.WriteByte('\"')\n\t}\n\n\tif !needsBackslash {\n\t\tb.WriteString(arg)\n\t\tb.WriteByte('\"')\n\t\treturn b.String()\n\t}\n\n\tfor _, c := range arg {\n\t\tswitch c {\n\t\tdefault:\n\t\t\tslashOut()\n\t\t\tb.WriteRune(c)\n\t\tcase '\\\\':\n\t\t\tslashes++\n\t\tcase '\"':\n\t\t\tslashOutBeforeQuote(true)\n\t\t\tb.WriteByte('\"')\n\t\t}\n\t}\n\tif needsQuote {\n\t\tslashOutBeforeQuote(false)\n\t\tb.WriteByte('\"')\n\t} else {\n\t\tslashOut()\n\t}\n\treturn b.String()\n}\n\nfunc ShellArgsString(args []string) string {\n\tb := strings.Builder{}\n\tfor i, a := range args {\n\t\tif i > 0 {\n\t\t\tb.WriteByte(' ')\n\t\t}\n\t\tb.WriteString(quoteArg(a))\n\t}\n\treturn b.String()\n}\n"
  },
  {
    "path": "pkg/shellquote/shellstring_unix.go",
    "content": "//go:build !windows\n\npackage shellquote\n\nimport (\n\t\"io\"\n\t\"strings\"\n)\n\nfunc quoteArg(arg string) string {\n\treturn Unix(arg)\n}\n\n// Split the given string into an array, using shell quote semantics.\nfunc Split(line string) ([]string, error) {\n\tif line == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tsb := strings.Builder{}\n\tparseDQSegment := func(s string) (string, int) {\n\t\tescaped := false\n\t\tfor i, r := range s {\n\t\t\tif escaped {\n\t\t\t\tescaped = false\n\t\t\t\tswitch r {\n\t\t\t\tcase '\"', '$', '\\\\':\n\t\t\t\t\tsb.WriteRune(r)\n\t\t\t\t// Skip escape character and write this one verbatim\n\t\t\t\tcase '\\n': // Escaped newline means concatenate the lines\n\t\t\t\tdefault:\n\t\t\t\t\tsb.WriteByte('\\\\') // Not known escape, so retain the escape character\n\t\t\t\t\tsb.WriteRune(r)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif r == '\"' {\n\t\t\t\t\treturn sb.String(), i + 2\n\t\t\t\t}\n\t\t\t\tif r == '\\\\' {\n\t\t\t\t\tescaped = true\n\t\t\t\t} else {\n\t\t\t\t\tsb.WriteRune(r)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn \"\", -1\n\t}\n\tparseSQSegment := func(s string) (string, int) {\n\t\tfor i, r := range s {\n\t\t\tif r == '\\'' {\n\t\t\t\treturn sb.String(), i + 2\n\t\t\t}\n\t\t\tsb.WriteRune(r)\n\t\t}\n\t\treturn \"\", -1\n\t}\n\n\tparseUQSegment := func(s string) (string, int) {\n\t\tescaped := false\n\t\tfor i, r := range s {\n\t\t\tif escaped {\n\t\t\t\tescaped = false\n\t\t\t\tswitch r {\n\t\t\t\tcase '\\n': // Escaped newline means concatenate the lines\n\t\t\t\tdefault: // For all other cases, just skip the escape character and write the rune verbatim\n\t\t\t\t\tsb.WriteRune(r)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch r {\n\t\t\t\tcase '\"', '\\'', ' ', '\\t', '\\r', '\\n': // start of quoted string or whitespace ends this segment\n\t\t\t\t\treturn sb.String(), i\n\t\t\t\tcase '\\\\':\n\t\t\t\t\tescaped = true\n\t\t\t\tdefault:\n\t\t\t\t\tsb.WriteRune(r)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn sb.String(), len(s)\n\t}\n\n\tvar ss []string\n\te := -1\n\tnewArg := true\n\tfor i, r := range line {\n\t\tif i < e {\n\t\t\tcontinue\n\t\t}\n\t\tvar s string\n\t\tvar x int\n\t\tswitch r {\n\t\tcase ' ', '\\t', '\\r', '\\n':\n\t\t\t// skip whitespace\n\t\t\tsb.Reset()\n\t\t\tnewArg = true\n\t\t\tcontinue\n\t\tcase '\"':\n\t\t\ts, x = parseDQSegment(line[i+1:])\n\t\tcase '\\'':\n\t\t\ts, x = parseSQSegment(line[i+1:])\n\t\tdefault:\n\t\t\ts, x = parseUQSegment(line[i:])\n\t\t}\n\t\tif x < 0 {\n\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t}\n\t\te = i + x\n\t\tif newArg {\n\t\t\tss = append(ss, s)\n\t\t\tnewArg = false\n\t\t} else {\n\t\t\tss[len(ss)-1] = s\n\t\t}\n\t}\n\treturn ss, nil\n}\n"
  },
  {
    "path": "pkg/shellquote/shellstring_unix_test.go",
    "content": "//go:build !windows\n\npackage shellquote\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSplit(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tline    string\n\t\twant    []string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"Empty\",\n\t\t\tline:    \"\",\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"single quoted\",\n\t\t\tline:    `'one quoted' 'two quoted'`,\n\t\t\twant:    []string{`one quoted`, `two quoted`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"escape in single quoted\",\n\t\t\tline:    `'\\one'`,\n\t\t\twant:    []string{`\\one`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"escape in single quoted\",\n\t\t\tline:    `'\\'one'`, // unbalanced. There's no escape in single quote\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"single quoted concat\",\n\t\t\tline:    `'one quoted''two quoted'`,\n\t\t\twant:    []string{`one quotedtwo quoted`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted\",\n\t\t\tline:    `\"one quoted\" \"two quoted\"`,\n\t\t\twant:    []string{`one quoted`, `two quoted`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted concat\",\n\t\t\tline:    `\"one quoted\"\"two quoted\"`,\n\t\t\twant:    []string{`one quotedtwo quoted`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted with escaped quote\",\n\t\t\tline:    `\"one \\\"quoted\\\"\" \"two quoted\"`,\n\t\t\twant:    []string{`one \"quoted\"`, `two quoted`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted with unbalanced escaped quote\",\n\t\t\tline:    `\"one \\\"quoted\\\" \"two quoted\"`,\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted with escaped dollar\",\n\t\t\tline:    `\"\\$32.0\"`,\n\t\t\twant:    []string{`$32.0`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted with escaped escape\",\n\t\t\tline:    `\"the \\\\ character\"`,\n\t\t\twant:    []string{`the \\ character`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted with escaped newline\",\n\t\t\tline:    \"\\\"the line \\\\\\ncontinues here\\\"\",\n\t\t\twant:    []string{`the line continues here`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted with escaped newline\",\n\t\t\tline:    \"\\\"the line \\\\\\ncontinues here\\\"\",\n\t\t\twant:    []string{`the line continues here`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"not quoted\",\n\t\t\tline:    `not quoted`,\n\t\t\twant:    []string{`not`, `quoted`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"backslash escape\",\n\t\t\tline:    `one\\ two three\\ four`,\n\t\t\twant:    []string{`one two`, `three four`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double and singe quoted concat\",\n\t\t\tline:    `\"one quoted\"'two quoted'`,\n\t\t\twant:    []string{`one quotedtwo quoted`},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := Split(tt.line)\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tt.want, got)\n\t\t\t\tgot, err := Split(ShellArgsString(got))\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tt.want, got)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/shellquote/shellstring_windows.go",
    "content": "package shellquote\n\nimport (\n\t\"io\"\n\t\"strings\"\n)\n\nfunc quoteArg(arg string) string {\n\treturn Windows(arg)\n}\n\n// Split the given string into an array, using shell quote semantics.\nfunc Split(line string) ([]string, error) {\n\tif line == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tsb := strings.Builder{}\n\tslashes := 0\n\tslashOut := func() {\n\t\tfor ; slashes > 0; slashes-- {\n\t\t\tsb.WriteByte('\\\\')\n\t\t}\n\t}\n\tslashOutBeforeQuote := func() bool {\n\t\teven := slashes&1 == 0\n\t\tslashes >>= 1\n\t\tslashOut()\n\t\treturn even\n\t}\n\tparseDQSegment := func(s string) (string, int) {\n\t\tslashes = 0\n\t\tfor i, r := range s {\n\t\t\tswitch r {\n\t\t\tcase '\"':\n\t\t\t\tif slashOutBeforeQuote() {\n\t\t\t\t\treturn sb.String(), i + 2\n\t\t\t\t}\n\t\t\t\tsb.WriteRune(r)\n\t\t\tcase '\\\\':\n\t\t\t\tslashes++\n\t\t\tdefault:\n\t\t\t\tslashOut()\n\t\t\t\tsb.WriteRune(r)\n\t\t\t}\n\t\t}\n\t\treturn \"\", -1\n\t}\n\tparseUQSegment := func(s string) (string, int) {\n\t\tslashes = 0\n\t\tfor i, r := range s {\n\t\t\tswitch r {\n\t\t\tcase ' ', '\\t', '\\r', '\\n': // start of quoted string or whitespace ends this segment\n\t\t\t\tslashOut()\n\t\t\t\treturn sb.String(), i\n\t\t\tcase '\"':\n\t\t\t\tif slashOutBeforeQuote() {\n\t\t\t\t\treturn sb.String(), i\n\t\t\t\t}\n\t\t\t\tsb.WriteByte('\"')\n\t\t\tcase '\\\\':\n\t\t\t\tslashes++\n\t\t\tdefault:\n\t\t\t\tslashOut()\n\t\t\t\tsb.WriteRune(r)\n\t\t\t}\n\t\t}\n\t\treturn sb.String(), len(s)\n\t}\n\n\tvar ss []string\n\te := -1\n\tnewArg := true\n\tfor i, r := range line {\n\t\tif i < e {\n\t\t\tcontinue\n\t\t}\n\t\tvar s string\n\t\tvar x int\n\t\tswitch r {\n\t\tcase ' ', '\\t', '\\r', '\\n':\n\t\t\t// skip whitespace\n\t\t\tsb.Reset()\n\t\t\tnewArg = true\n\t\t\tcontinue\n\t\tcase '\"':\n\t\t\ts, x = parseDQSegment(line[i+1:])\n\t\tdefault:\n\t\t\ts, x = parseUQSegment(line[i:])\n\t\t}\n\t\tif x < 0 {\n\t\t\treturn nil, io.ErrUnexpectedEOF\n\t\t}\n\t\te = i + x\n\t\tif newArg {\n\t\t\tss = append(ss, s)\n\t\t\tnewArg = false\n\t\t} else {\n\t\t\tss[len(ss)-1] = s\n\t\t}\n\t}\n\treturn ss, nil\n}\n"
  },
  {
    "path": "pkg/shellquote/shellstring_windows_test.go",
    "content": "package shellquote\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestSplit(t *testing.T) {\n\t// Tests inspired by https://docs.microsoft.com/en-us/previous-versions/ms880421(v=msdn.10)?redirectedfrom=MSDN\n\ttests := []struct {\n\t\tname    string\n\t\tline    string\n\t\twant    []string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"Empty\",\n\t\t\tline:    \"\",\n\t\t\twant:    nil,\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    \"double quoted with unbalanced escaped quote\",\n\t\t\tline:    `\"one \\\"quoted\\\" \"two quoted\"`,\n\t\t\twant:    nil,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    `\"a b c\" d e`,\n\t\t\tline:    `\"a b c\" d e`,\n\t\t\twant:    []string{`a b c`, `d`, `e`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    `\"ab\\\"c\" \"\\\\\" d`,\n\t\t\tline:    `\"ab\\\"c\" \"\\\\\" d`,\n\t\t\twant:    []string{`ab\"c`, `\\`, `d`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    `a\\\\\\b d\"e f\"g h`,\n\t\t\tline:    `a\\\\\\b d\"e f\"g h`,\n\t\t\twant:    []string{`a\\\\\\b`, `de fg`, `h`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    `a\\\\\\\"b c d`,\n\t\t\tline:    `a\\\\\\\"b c d`,\n\t\t\twant:    []string{`a\\\"b`, `c`, `d`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    `a\\\\\\\\\"b c\" d e`,\n\t\t\tline:    `a\\\\\\\\\"b c\" d e`,\n\t\t\twant:    []string{`a\\\\b c`, `d`, `e`},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname:    `\"a\\\\b c\" d e`,\n\t\t\tline:    `\"a\\\\b c\" d e`,\n\t\t\twant:    []string{`a\\\\b c`, `d`, `e`},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := Split(tt.line)\n\t\t\tif tt.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\tassert.Equal(t, tt.want, got)\n\t\t\t\tjoined := ShellArgsString(got)\n\t\t\t\tgot, err := Split(joined)\n\t\t\t\trequire.NoError(t, err, joined)\n\t\t\t\tassert.Equal(t, tt.want, got, joined)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc Test_quoteArg(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\targ  string\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: `\\`,\n\t\t\targ:  `\\`,\n\t\t\twant: `\\`,\n\t\t},\n\t\t{\n\t\t\tname: `\\ \\`,\n\t\t\targ:  `\\ \\`,\n\t\t\twant: `\"\\ \\\\\"`,\n\t\t},\n\t\t{\n\t\t\tname: `\\ \\\"`,\n\t\t\targ:  `\\ \\\"`,\n\t\t\twant: `\"\\ \\\\\\\"\"`,\n\t\t},\n\t\t{\n\t\t\tname: `\\\\ \\\\`,\n\t\t\targ:  `\\\\ \\\\`,\n\t\t\twant: `\"\\\\ \\\\\\\\\"`,\n\t\t},\n\t\t{\n\t\t\tname: `\\\" \\\\`,\n\t\t\targ:  `\\\" \\\\`,\n\t\t\twant: `\"\\\\\\\" \\\\\\\\\"`,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, []byte(tt.want), []byte(quoteArg(tt.arg)), \"quoteArg(%v)\", tt.arg)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/sigctx/context.go",
    "content": "package sigctx\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"os/signal\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/proc\"\n)\n\n// DoWithSignalHandler runs f with a context canceled when an INTERRUPT or TERMINATE signal is received.\nfunc DoWithSignalHandler(ctx context.Context, f func(context.Context) error) error {\n\tctx, cancel := context.WithCancel(ctx)\n\tsigs := make(chan os.Signal, 1)\n\n\tgo func() {\n\t\tselect {\n\t\tcase sig := <-sigs:\n\t\t\tclog.Infof(ctx, \"Received %s, shutting down\", sig)\n\t\t\tcancel()\n\t\tcase <-ctx.Done():\n\t\t}\n\t\tsignal.Stop(sigs)\n\t\tclose(sigs)\n\t}()\n\tsignal.Notify(sigs, proc.SignalsToForward...)\n\treturn f(ctx)\n}\n"
  },
  {
    "path": "pkg/slice/contains.go",
    "content": "package slice\n\n// AppendUnique appends all elements that are not already present in dest to dest\n// and returns the result.\nfunc AppendUnique[E comparable](dest []E, src ...E) []E {\n\tfor _, v := range src {\n\t\tif !Contains(dest, v) {\n\t\t\tdest = append(dest, v)\n\t\t}\n\t}\n\treturn dest\n}\n\n// Contains returns true if the given slice contains the given element.\nfunc Contains[E comparable](vs []E, e E) bool {\n\tfor _, v := range vs {\n\t\tif e == v {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// ContainsAll returns true if the first slice contains all elements in the second slice.\nfunc ContainsAll[E comparable](vs []E, es []E) bool {\n\tfor _, e := range es {\n\t\tif !Contains(vs, e) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// ContainsAny returns true if the first slice contains at least one of the elements in the second slice.\nfunc ContainsAny[E comparable](vs []E, es []E) bool {\n\tfor _, e := range es {\n\t\tif Contains(vs, e) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/slice/csv.go",
    "content": "package slice\n\nimport (\n\t\"bytes\"\n\t\"encoding/csv\"\n\t\"strings\"\n)\n\n// AsCSV returns the string slice encoded by a csv.NewWriter.\nfunc AsCSV(vs []string) string {\n\tb := &bytes.Buffer{}\n\tw := csv.NewWriter(b)\n\tif err := w.Write(vs); err != nil {\n\t\t// The underlying bytes.Buffer should never error.\n\t\tpanic(err)\n\t}\n\tw.Flush()\n\treturn strings.TrimSuffix(b.String(), \"\\n\")\n}\n"
  },
  {
    "path": "pkg/slice/strings.go",
    "content": "package slice\n\nimport \"fmt\"\n\nfunc AsStrings[T fmt.Stringer](vs []T) []string {\n\tss := make([]string, len(vs))\n\tfor i, v := range vs {\n\t\tss[i] = v.String()\n\t}\n\treturn ss\n}\n"
  },
  {
    "path": "pkg/subnet/bitfield256.go",
    "content": "package subnet\n\nimport (\n\t\"fmt\"\n\t\"math/bits\"\n)\n\n// Bitfield256 represents 0 - 255 unique bytes.\ntype Bitfield256 [4]uint64\n\n// SetBit sets the 1<<bv bit of the bitfield to 1.\nfunc (b *Bitfield256) SetBit(bv byte) {\n\tb[bv>>6] |= uint64(1) << uint64(bv&0x3f)\n}\n\n// ClearBit clears the 1<<bv bit of the bitfield to 0.\nfunc (b *Bitfield256) ClearBit(bv byte) {\n\tb[bv>>6] &^= uint64(1) << uint64(bv&0x3f)\n}\n\n// GetBit returns the value of the 1<<bv bit of the bitfield (0 is false, 1 is true).\nfunc (b *Bitfield256) GetBit(bv byte) bool {\n\treturn b[bv>>6]&(uint64(1)<<uint64(bv&0x3f)) != 0\n}\n\n// Equals returns true if this Bitfield256 equals the argument.\nfunc (b *Bitfield256) Equals(other *Bitfield256) bool {\n\tif other == nil {\n\t\treturn false\n\t}\n\treturn *b == *other\n}\n\n// OnesCount returns the number of 1 bits in the bitfield.\nfunc (b *Bitfield256) OnesCount() (l int) {\n\tfor _, g := range b {\n\t\tif g != 0 {\n\t\t\tl += bits.OnesCount64(g)\n\t\t}\n\t}\n\treturn l\n}\n\n// String prints the hexadecimal representation of the bits.\nfunc (b *Bitfield256) String() string {\n\treturn fmt.Sprintf(\"%0.16x%0.16x%0.16x%0.16x\", b[0], b[1], b[2], b[3])\n}\n\n// ToSlice returns an ordered slice of all bytes in this Bitfield256.\nfunc (b *Bitfield256) ToSlice() []byte {\n\tl := b.OnesCount() // faster and more accurate than repeatedly growing a slice\n\tif l == 0 {\n\t\treturn []byte{}\n\t}\n\tslice := make([]byte, l)\n\ti := 0\n\tfor bi, g := range b {\n\t\tif g != 0 {\n\t\t\tbx := bi << 6\n\t\t\tfor bit := 0; bit < 64; bit++ {\n\t\t\t\tif g&(uint64(1)<<bit) != 0 {\n\t\t\t\t\tslice[i] = byte(bx | bit)\n\t\t\t\t\ti++\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn slice\n}\n\n// Mask returns how many bits, from left to right, that have the same\n// value for all bytes represented by this Bitfield256 and a byte containing\n// the value of those bits.\nfunc (b *Bitfield256) Mask() (ones int, value byte) {\n\tfor testBit := 7; testBit >= 0; testBit-- {\n\t\thasBit := false\n\t\tfirst := true\n\t\tv := 1 << testBit\n\t\tfor i, g := range b {\n\t\t\tif g != 0 {\n\t\t\t\tbx := i << 6 // top two bits of bytes in this group\n\t\t\t\tfor bit := 0; bit < 64; bit++ {\n\t\t\t\t\tif g&(uint64(1)<<bit) != 0 {\n\t\t\t\t\t\tbv := bx | bit\n\t\t\t\t\t\tif first {\n\t\t\t\t\t\t\tfirst = false\n\t\t\t\t\t\t\thasBit = bv&v != 0\n\t\t\t\t\t\t} else if hasBit != (bv&v != 0) {\n\t\t\t\t\t\t\treturn 7 - testBit, value\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif hasBit {\n\t\t\tvalue |= byte(v)\n\t\t}\n\t}\n\treturn 8, value\n}\n"
  },
  {
    "path": "pkg/subnet/bitfield256_test.go",
    "content": "package subnet\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestByteSet_Add(t *testing.T) {\n\tx := *fullSet\n\tx.SetBit(1)\n\tassert.True(t, x.Equals(fullSet))\n\n\tx = *emptySet\n\tx.SetBit(1)\n\tassert.False(t, x.Equals(emptySet))\n\tassert.True(t, x.GetBit(1))\n\tassert.False(t, x.GetBit(233))\n\tx.SetBit(233)\n\tassert.True(t, x.GetBit(233))\n\tassert.Equal(t, 2, x.OnesCount())\n}\n\nfunc TestByteSet_Remove(t *testing.T) {\n\tx := *fullSet\n\tx.ClearBit(255)\n\tassert.Equal(t, 255, x.OnesCount())\n\tx.ClearBit(0)\n\tassert.Equal(t, 254, x.OnesCount())\n}\n\nfunc TestByteSet_Mask(t *testing.T) {\n\tbytes00To0F := &Bitfield256{}\n\tfor i := 0; i < 0xf; i++ {\n\t\tbytes00To0F.SetBit(byte(i))\n\t}\n\tbytesF0ToFF := &Bitfield256{}\n\tfor i := 0xf0; i < 0xff; i++ {\n\t\tbytesF0ToFF.SetBit(byte(i))\n\t}\n\ttests := []struct {\n\t\tname      string\n\t\tset       *Bitfield256\n\t\twantOnes  int\n\t\twantValue byte\n\t}{\n\t\t{\n\t\t\t\"full set\",\n\t\t\tfullSet,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"empty set\",\n\t\t\temptySet,\n\t\t\t8,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"00 to 0f\",\n\t\t\tbytes00To0F,\n\t\t\t4,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"f0 to ff\",\n\t\t\tbytesF0ToFF,\n\t\t\t4,\n\t\t\t0xf0,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgotOnes, gotValue := tt.set.Mask()\n\t\t\tif gotOnes != tt.wantOnes {\n\t\t\t\tt.Errorf(\"Mask() gotOnes = %v, want %v\", gotOnes, tt.wantOnes)\n\t\t\t}\n\t\t\tif gotValue != tt.wantValue {\n\t\t\t\tt.Errorf(\"Mask() gotValue = %v, want %v\", gotValue, tt.wantValue)\n\t\t\t}\n\t\t})\n\t}\n}\n\nvar (\n\temptySet = &Bitfield256{}\n\tfullSet  = &Bitfield256{0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff}\n)\n\nfunc TestByteSet_String(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tset  *Bitfield256\n\t\twant string\n\t}{\n\t\t{\n\t\t\t\"full set\",\n\t\t\tfullSet,\n\t\t\t\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\",\n\t\t},\n\t\t{\n\t\t\t\"empty set\",\n\t\t\temptySet,\n\t\t\t\"0000000000000000000000000000000000000000000000000000000000000000\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.set.String(); got != tt.want {\n\t\t\t\tt.Errorf(\"String() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestByteSet_ToSlice(t *testing.T) {\n\ts := emptySet.ToSlice()\n\tassert.Equal(t, 0, len(s))\n\ts = fullSet.ToSlice()\n\tassert.Equal(t, 256, len(s))\n\tfor i := 0; i < 256; i++ {\n\t\tassert.Equal(t, byte(i), s[i])\n\t}\n}\n"
  },
  {
    "path": "pkg/subnet/set.go",
    "content": "package subnet\n\nimport (\n\t\"net/netip\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/maps\"\n)\n\n// Set represents a unique unordered set of subnets.\ntype Set map[netip.Prefix]struct{}\n\nfunc NewSet(subnets []netip.Prefix) Set {\n\ts := make(Set, len(subnets))\n\tfor _, subnet := range subnets {\n\t\ts.Add(subnet)\n\t}\n\treturn s\n}\n\n// Equals returns true if the two sets have the same content.\nfunc (s Set) Equals(o Set) bool {\n\tif len(s) != len(o) {\n\t\treturn false\n\t}\n\tfor key := range s {\n\t\tif _, ok := o[key]; !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// AppendSortedTo appends the sorted subnets of this set to the given slice and returns\n// the resulting slice.\nfunc (s Set) AppendSortedTo(subnets []netip.Prefix) []netip.Prefix {\n\tsz := len(s)\n\tif sz == 0 {\n\t\treturn subnets\n\t}\n\t// Ensure capacity of the slice\n\tneed := len(subnets) + sz\n\tif cap(subnets) < need {\n\t\tns := make([]netip.Prefix, len(subnets), need)\n\t\tcopy(ns, subnets)\n\t\tsubnets = ns\n\t}\n\treturn append(subnets, s.sortedKeys()...)\n}\n\n// Add adds a subnet to this set unless it doesn't already exist. Returns true if the subnet was added, false otherwise.\nfunc (s Set) Add(subnet netip.Prefix) bool {\n\tif _, ok := s[subnet]; ok {\n\t\treturn false\n\t}\n\ts[subnet] = struct{}{}\n\treturn true\n}\n\n// Delete deletes a subnet equal to the given subnet. Returns true if the subnet was deleted, false otherwise.\nfunc (s Set) Delete(subnet netip.Prefix) bool {\n\tif _, ok := s[subnet]; ok {\n\t\tdelete(s, subnet)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// Clone returns a copy of this Set.\nfunc (s Set) Clone() Set {\n\treturn maps.Copy(s)\n}\n\nfunc (s Set) String() string {\n\tif s == nil {\n\t\treturn \"nil\"\n\t}\n\tsb := strings.Builder{}\n\tsb.WriteByte('[')\n\tfor i, key := range s.sortedKeys() {\n\t\tif i > 0 {\n\t\t\tsb.WriteByte(' ')\n\t\t}\n\t\tsb.WriteString(key.String())\n\t}\n\tsb.WriteByte(']')\n\treturn sb.String()\n}\n\nfunc (s Set) sortedKeys() []netip.Prefix {\n\tks := make([]netip.Prefix, len(s))\n\ti := 0\n\tfor k := range s {\n\t\tks[i] = k\n\t\ti++\n\t}\n\tsort.Slice(ks, func(i, j int) bool {\n\t\tia := ks[i].Addr()\n\t\tja := ks[j].Addr()\n\t\tif ia == ja {\n\t\t\treturn ks[i].Bits() < ks[j].Bits()\n\t\t}\n\t\treturn ia.Less(ja)\n\t})\n\treturn ks\n}\n"
  },
  {
    "path": "pkg/subnet/set_test.go",
    "content": "package subnet\n\nimport (\n\t\"net/netip\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nvar (\n\toneCIDR   = netip.MustParsePrefix(\"192.168.0.0/24\")\n\ttwoCIDR   = netip.MustParsePrefix(\"192.168.1.0/24\")\n\tthreeCIDR = netip.MustParsePrefix(\"192.168.2.0/24\")\n)\n\nfunc TestSet_Add(t *testing.T) {\n\ttests := []struct {\n\t\tname  string\n\t\tcidrs []netip.Prefix\n\t\twant  Set\n\t}{\n\t\t{\n\t\t\tname:  \"works with nil\",\n\t\t\tcidrs: nil,\n\t\t\twant:  Set{},\n\t\t},\n\t\t{\n\t\t\tname:  \"adds uniquely\",\n\t\t\tcidrs: []netip.Prefix{oneCIDR, twoCIDR, oneCIDR, twoCIDR},\n\t\t\twant:  NewSet([]netip.Prefix{oneCIDR, twoCIDR}),\n\t\t},\n\t\t{\n\t\t\tname:  \"works with nil\",\n\t\t\tcidrs: nil,\n\t\t\twant:  NewSet(nil),\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := NewSet(tt.cidrs); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"makeCIDRMap() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSet_String(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\ts    Set\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"nil is ok\",\n\t\t\ts:    nil,\n\t\t\twant: \"nil\",\n\t\t},\n\t\t{\n\t\t\tname: \"zero elements is just brackets\",\n\t\t\ts:    Set{},\n\t\t\twant: \"[]\",\n\t\t},\n\t\t{\n\t\t\tname: \"one element is without space\",\n\t\t\ts:    NewSet([]netip.Prefix{oneCIDR}),\n\t\t\twant: \"[192.168.0.0/24]\",\n\t\t},\n\t\t{\n\t\t\tname: \"output is space separated and sorted\",\n\t\t\ts:    NewSet([]netip.Prefix{threeCIDR, oneCIDR, twoCIDR}),\n\t\t\twant: \"[192.168.0.0/24 192.168.1.0/24 192.168.2.0/24]\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.s.String(); got != tt.want {\n\t\t\t\tt.Errorf(\"String() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestSet_AppendSortedTo(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\ts       Set\n\t\tsubnets []netip.Prefix\n\t\twant    []netip.Prefix\n\t}{\n\t\t{\n\t\t\tname:    \"appends sorted\",\n\t\t\ts:       NewSet([]netip.Prefix{threeCIDR, oneCIDR, twoCIDR}),\n\t\t\tsubnets: []netip.Prefix{threeCIDR},\n\t\t\twant:    []netip.Prefix{threeCIDR, oneCIDR, twoCIDR, threeCIDR},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := tt.s.AppendSortedTo(tt.subnets); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"AppendSortedTo() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/subnet/subnet.go",
    "content": "// Package subnet contains functions for finding available subnets\npackage subnet\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"sort\"\n)\n\n// CoveringPrefixes returns the ip networks needed to cover the given IPs with as\n// big mask as possible for each subnet. The analysis starts by finding all\n// subnets using a 16-bit mask for IPv4 and a 64 bit mask for IPv6 addresses.\n// Once the subnets are established, the mask for each one will be increased\n// to the maximum value that still masks all IPs that it was created for.\nfunc CoveringPrefixes(addrs []netip.Addr) []netip.Prefix {\n\t// Divide into subnets with ByteSets.\n\t// IPv4 subnet key. Identifies a class B subnet\n\ttype ipv4SubnetKey [2]byte\n\n\t// IPv6 subnet key. This is the 48-bit route and 16-bit subnet identifier. Identifies a 64 bit subnet.\n\ttype ipv6SubnetKey [8]byte\n\n\tipv6Subnets := make(map[ipv6SubnetKey]*[7]Bitfield256)\n\n\t// IPv4 has 2 byte subnets and one Bitfield256 representing the third byte.\n\t// (last byte is skipped because no split on subnet is made on that byte).\n\tipv4Subnets := make(map[ipv4SubnetKey]*Bitfield256)\n\n\t// IPv6 has 8 byte subnets and seven ByteSets representing all but the last\n\t// byte of the subnet relative 64-bit address (last byte is skipped because\n\t// no split into subnets is made using that byte).\n\tfor _, ip := range addrs {\n\t\tif ip.Is4() {\n\t\t\tip4 := ip.As4()\n\t\t\tvar bytes *Bitfield256\n\t\t\tr := ipv4SubnetKey{ip4[0], ip4[1]}\n\t\t\tif bytes = ipv4Subnets[r]; bytes == nil {\n\t\t\t\tbytes = &Bitfield256{}\n\t\t\t\tipv4Subnets[r] = bytes\n\t\t\t}\n\t\t\tbytes.SetBit(ip4[2])\n\t\t} else if ip.Is6() {\n\t\t\tip16 := ip.As16()\n\t\t\tr := ipv6SubnetKey{}\n\t\t\tcopy(r[:], ip16[:8])\n\t\t\tbyteSets, ok := ipv6Subnets[r]\n\t\t\tif !ok {\n\t\t\t\tbyteSets = &[7]Bitfield256{}\n\t\t\t\tipv6Subnets[r] = byteSets\n\t\t\t}\n\t\t\tfor i := range byteSets {\n\t\t\t\tbyteSets[i].SetBit(ip16[i+8])\n\t\t\t}\n\t\t}\n\t}\n\n\tsubnets := make([]netip.Prefix, len(ipv4Subnets)+len(ipv6Subnets))\n\ti := 0\n\tfor network, bytes := range ipv4Subnets {\n\t\tones, thirdByte := bytes.Mask()\n\t\tsubnets[i] = netip.PrefixFrom(netip.AddrFrom4([4]byte{network[0], network[1], thirdByte, 0}), 16+ones)\n\t\ti++\n\t}\n\tfor subnet, byteSets := range ipv6Subnets {\n\t\tmaskOnes := 64\n\t\tip := [16]byte{}\n\t\tcopy(ip[:], subnet[:])\n\t\tfor bi, bytes := range byteSets {\n\t\t\tones, nByte := bytes.Mask()\n\t\t\tmaskOnes += ones\n\t\t\tip[8+bi] = nByte\n\t\t\tif ones != 8 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tsubnets[i] = netip.PrefixFrom(netip.AddrFrom16(ip), maskOnes)\n\t\ti++\n\t}\n\tsort.Slice(subnets, func(i, j int) bool { return subnets[i].Addr().Less(subnets[j].Addr()) })\n\treturn subnets\n}\n\n// Unique will drop any subnet that is covered by another subnet from the\n// given slice and return the resulting slice. This function will alter\n// the given slice.\nfunc Unique(subnets []netip.Prefix) []netip.Prefix {\n\tln := len(subnets)\n\tfor i := 0; i < ln; i++ {\n\t\tfor r := 0; r < ln; r++ {\n\t\t\tif r != i && Covers(subnets[r], subnets[i]) {\n\t\t\t\tln--\n\t\t\t\tif i < ln {\n\t\t\t\t\tsubnets[i] = subnets[ln]\n\t\t\t\t\ti--\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn subnets[:ln]\n}\n\n// Partition returns two slices, the first containing the subnets for which the filter evaluates\n// to true, the second containing the rest.\nfunc Partition[T any](subnets []T, filter func(int, T) bool) (matched, notMatched []T) {\n\tfor i, sn := range subnets {\n\t\tif filter(i, sn) {\n\t\t\tmatched = append(matched, sn)\n\t\t} else {\n\t\t\tnotMatched = append(notMatched, sn)\n\t\t}\n\t}\n\treturn matched, notMatched\n}\n\n// Covers answers the question if network range a contains the full network range b.\nfunc Covers(a, b netip.Prefix) bool {\n\tif !(a.IsValid() && b.IsValid()) {\n\t\treturn false\n\t}\n\tif a == b {\n\t\treturn true\n\t}\n\tif a.Addr().Is4() != b.Addr().Is4() {\n\t\treturn false\n\t}\n\tbts := a.Bits()\n\tif bts > b.Bits() {\n\t\treturn false\n\t}\n\tif bts > 0 {\n\t\tvar err error\n\t\tif a, err = a.Addr().Prefix(bts); err != nil {\n\t\t\treturn false\n\t\t}\n\t\tif b, err = b.Addr().Prefix(bts); err != nil {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn a.Addr() == b.Addr()\n}\n\n// incIP4 attempts to increase the given ip. The increase starts at the penultimate byte. The increased IP is\n// returned unless it is equal or larger than the given end, in which case nil is returned.\nfunc incIP4(ip, end netip.Addr) netip.Addr {\n\tipc := ip.As4()\n\tipc[3] = 0\n\tfor i := 2; i >= 0; i-- {\n\t\tif ipc[i] < 255 {\n\t\t\tipc[i]++\n\t\t\tip = netip.AddrFrom4(ipc)\n\t\t\tif !ip.Less(end) {\n\t\t\t\tip = netip.Addr{}\n\t\t\t}\n\t\t\tbreak\n\t\t} else {\n\t\t\tipc[i] = 0\n\t\t}\n\t}\n\treturn ip\n}\n\n// RandomIPv4Prefix finds a random free subnet using the given mask. A subnet is considered\n// free if it doesn't overlap with any of the subnets returned by the net.InterfaceAddrs\n// function or with any of the subnets provided in the avoid parameter.\n// The returned subnet will be a private IPv4 subnet in either class C, B, or A range, and the search\n// for a free subnet uses that order.\n// See https://en.wikipedia.org/wiki/Private_network for more info about private subnets.\nfunc RandomIPv4Prefix(bits int, avoid []netip.Prefix) (netip.Prefix, error) {\n\tas, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\treturn netip.Prefix{}, err\n\t}\n\tcidrs := make([]netip.Prefix, 0, len(as)+len(avoid))\n\tfor _, a := range as {\n\t\tif cidr, err := netip.ParsePrefix(a.String()); err == nil {\n\t\t\tcidrs = append(cidrs, cidr)\n\t\t}\n\t}\n\tavoid = Unique(append(cidrs, avoid...))\n\n\t// IP address range pairs, from - to (to is non-inclusive)\n\tranges := []netip.Addr{\n\t\tnetip.AddrFrom4([4]byte{192, 168, 0, 0}), netip.AddrFrom4([4]byte{192, 169, 0, 0}), // Class C private range\n\t\tnetip.AddrFrom4([4]byte{172, 16, 0, 0}), netip.AddrFrom4([4]byte{172, 32, 0, 0}), // Class B private range\n\t\tnetip.AddrFrom4([4]byte{10, 0, 0, 0}), netip.AddrFrom4([4]byte{11, 0, 0, 0}), // Class A private range\n\t}\n\n\tfor i := 0; i < len(ranges); i += 2 {\n\t\tip := ranges[i]\n\t\tend := ranges[i+1]\n\t\tfor {\n\t\t\tip1 := ip.As4()\n\t\t\tip1[3] = 1\n\t\t\tsn := netip.PrefixFrom(netip.AddrFrom4(ip1), bits)\n\t\t\tif !slices.ContainsFunc(avoid, func(cidr netip.Prefix) bool { return cidr.Overlaps(sn) }) {\n\t\t\t\treturn sn, nil\n\t\t\t}\n\t\t\tif ip = incIP4(ip, end); !ip.IsValid() {\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\treturn netip.Prefix{}, fmt.Errorf(\"unable to find a free subnet\")\n}\n\n// IsHalfOfDefault route returns true if the given subnet covers half the address space with a /1 mask.\nfunc IsHalfOfDefault(n netip.Prefix) bool {\n\treturn n.Bits() == 1\n}\n\nfunc PrefixToIPNet(p netip.Prefix) *net.IPNet {\n\tif !p.IsValid() {\n\t\treturn nil\n\t}\n\ta := p.Addr()\n\treturn &net.IPNet{\n\t\tIP:   a.AsSlice(),\n\t\tMask: net.CIDRMask(p.Bits(), a.BitLen()),\n\t}\n}\n"
  },
  {
    "path": "pkg/subnet/subnet_test.go",
    "content": "package subnet\n\nimport (\n\t\"bufio\"\n\t\"net/netip\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_covers(t *testing.T) {\n\tnetwork1, _ := netip.ParsePrefix(\"10.127.0.0/16\")\n\tnetwork2, _ := netip.ParsePrefix(\"10.127.201.0/24\")\n\tassert.True(t, Covers(network1, network2))\n\n\tnetwork2, _ = netip.ParsePrefix(\"10.127.202.0/24\")\n\tassert.True(t, Covers(network1, network2))\n\n\tnetwork2, _ = netip.ParsePrefix(\"10.127.0.0/16\")\n\tassert.True(t, Covers(network1, network2))\n\n\tnetwork2, _ = netip.ParsePrefix(\"10.127.0.0/18\")\n\tassert.True(t, Covers(network1, network2))\n\n\tnetwork2, _ = netip.ParsePrefix(\"10.124.0.0/14\")\n\tassert.False(t, Covers(network1, network2))\n\n\tnetwork2, _ = netip.ParsePrefix(\"10.127.201.0/8\")\n\tassert.False(t, Covers(network1, network2))\n\n\tnetwork2, _ = netip.ParsePrefix(\"10.128.0.0/16\")\n\tassert.False(t, Covers(network1, network2))\n\n\tnetwork1, _ = netip.ParsePrefix(\"10.127.192.0/18\")\n\tnetwork2, _ = netip.ParsePrefix(\"10.127.192.0/18\")\n\tassert.True(t, Covers(network1, network2))\n\n\tnetwork2, _ = netip.ParsePrefix(\"10.127.0.0/16\")\n\tassert.False(t, Covers(network1, network2))\n\n\tnetwork2, _ = netip.ParsePrefix(\"10.127.192.0/19\")\n\tassert.True(t, Covers(network1, network2))\n\n\tnetwork1, _ = netip.ParsePrefix(\"192.168.0.0/21\")\n\tnetwork2, _ = netip.ParsePrefix(\"192.168.8.0/24\")\n\tassert.False(t, Covers(network1, network2))\n}\n\nfunc TestCoveringPrefixes(t *testing.T) {\n\tips := loadAddrs(t)\n\tipNets := CoveringPrefixes(ips)\n\trequire.Equal(t, 4, len(ipNets))\n\trequire.Equal(t, netip.PrefixFrom(netip.AddrFrom4([4]byte{10, 101, 128, 0}), 18), ipNets[0])\n\trequire.Equal(t, netip.PrefixFrom(netip.AddrFrom4([4]byte{172, 20, 0, 0}), 16), ipNets[1])\n\trequire.Equal(t, netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01, 0x0d, 0xb8, 0x33, 0x33, 0x44, 0x44, 0x55, 0x55, 0x66, 0x66, 0x77, 0x00, 0x00, 0x00}), 104), ipNets[2])\n\trequire.Equal(t, netip.PrefixFrom(netip.AddrFrom16([16]byte{0x20, 0x01, 0x0d, 0xb8, 0x33, 0x33, 0xab, 0x32, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}), 79), ipNets[3])\n}\n\nfunc loadAddrs(t *testing.T) []netip.Addr {\n\tipf, err := os.Open(\"testdata/ips.txt\")\n\trequire.NoError(t, err)\n\tdefer ipf.Close()\n\n\tips := make([]netip.Addr, 0, 1500)\n\trd := bufio.NewScanner(ipf)\n\tfor rd.Scan() {\n\t\tip, err := netip.ParseAddr(rd.Text())\n\t\tif err != nil {\n\t\t\tt.Fatal(\"bad ip\")\n\t\t}\n\t\tips = append(ips, ip)\n\t}\n\treturn ips\n}\n\nfunc TestUnique(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tsubnets []netip.Prefix\n\t\twant    []netip.Prefix\n\t}{\n\t\t{\n\t\t\tname: \"Removes equal subnets\",\n\t\t\tsubnets: []netip.Prefix{\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.172.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.172.0.0/16\"),\n\t\t\t},\n\t\t\twant: []netip.Prefix{\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.172.0.0/16\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Removes covered subnets\",\n\t\t\tsubnets: []netip.Prefix{\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/24\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.172.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t},\n\t\t\twant: []netip.Prefix{\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.172.0.0/16\"),\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"Removes covered subnets reverse\",\n\t\t\tsubnets: []netip.Prefix{\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.172.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/24\"),\n\t\t\t},\n\t\t\twant: []netip.Prefix{\n\t\t\t\tnetip.MustParsePrefix(\"192.168.0.0/16\"),\n\t\t\t\tnetip.MustParsePrefix(\"192.172.0.0/16\"),\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := Unique(tt.subnets); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"Unique() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/subnet/testdata/ips.txt",
    "content": "172.20.125.87\n172.20.125.87\n172.20.121.244\n172.20.121.244\n172.20.123.214\n172.20.123.214\n172.20.71.183\n172.20.71.183\n172.20.200.221\n172.20.200.221\n172.20.23.11\n172.20.23.11\n172.20.147.76\n172.20.147.76\n172.20.79.161\n172.20.79.161\n172.20.229.3\n172.20.229.3\n172.20.157.176\n172.20.157.176\n172.20.168.50\n172.20.168.50\n172.20.91.165\n172.20.91.165\n172.20.169.197\n172.20.169.197\n172.20.7.85\n172.20.7.85\n172.20.234.203\n172.20.234.203\n172.20.56.233\n172.20.56.233\n172.20.62.137\n172.20.62.137\n172.20.124.232\n172.20.124.232\n172.20.36.194\n172.20.36.194\n172.20.105.220\n172.20.105.220\n172.20.143.24\n172.20.143.24\n172.20.221.169\n172.20.221.169\n172.20.66.12\n172.20.66.12\n172.20.243.144\n172.20.243.144\n172.20.157.166\n172.20.157.166\n172.20.247.46\n172.20.247.46\n172.20.27.230\n172.20.27.230\n172.20.106.180\n172.20.106.180\n172.20.135.196\n172.20.135.196\n172.20.221.50\n172.20.221.50\n172.20.134.248\n172.20.134.248\n172.20.150.154\n172.20.150.154\n172.20.140.53\n172.20.140.53\n10.101.147.51\n10.101.176.135\n10.101.178.135\n10.101.176.177\n10.101.151.2\n10.101.144.160\n10.101.147.195\n10.101.176.144\n10.101.179.211\n10.101.145.16\n10.101.147.181\n10.101.154.55\n10.101.176.31\n10.101.178.219\n10.101.146.234\n10.101.179.71\n10.101.146.1\n10.101.146.36\n10.101.178.91\n10.101.172.195\n10.101.150.251\n10.101.177.73\n10.101.151.137\n10.101.177.95\n10.101.151.89\n10.101.179.219\n10.101.144.108\n10.101.145.24\n10.101.150.236\n10.101.179.59\n10.101.169.208\n172.20.11.142\n172.20.11.142\n172.20.11.142\n172.20.73.146\n172.20.142.154\n172.20.142.154\n10.101.176.175\n10.101.162.245\n10.101.167.41\n10.101.149.204\n10.101.178.22\n10.101.167.234\n10.101.162.78\n10.101.177.202\n10.101.176.221\n10.101.165.103\n10.101.165.5\n10.101.166.177\n10.101.177.15\n10.101.163.84\n10.101.167.19\n10.101.160.203\n10.101.176.251\n10.101.162.21\n10.101.160.183\n10.101.150.37\n10.101.167.6\n10.101.158.79\n10.101.167.96\n10.101.173.142\n10.101.167.3\n10.101.178.145\n10.101.160.228\n10.101.158.175\n10.101.171.12\n10.101.152.197\n10.101.147.22\n10.101.152.59\n10.101.177.132\n10.101.162.23\n10.101.165.183\n10.101.178.134\n10.101.178.133\n10.101.160.13\n10.101.163.249\n10.101.163.227\n10.101.174.123\n10.101.164.200\n10.101.149.84\n10.101.165.67\n10.101.165.238\n10.101.164.100\n10.101.152.160\n10.101.160.194\n10.101.158.162\n10.101.163.96\n10.101.167.89\n10.101.175.129\n10.101.166.126\n10.101.158.129\n10.101.168.255\n10.101.160.232\n10.101.156.160\n10.101.154.245\n10.101.166.226\n10.101.151.63\n10.101.153.62\n10.101.178.212\n10.101.179.4\n10.101.154.3\n10.101.152.131\n10.101.156.73\n10.101.152.110\n10.101.159.139\n10.101.147.110\n10.101.179.50\n10.101.175.45\n10.101.157.225\n10.101.179.69\n10.101.150.7\n10.101.172.247\n10.101.164.176\n10.101.162.83\n10.101.163.1\n10.101.147.118\n10.101.162.193\n10.101.165.228\n10.101.178.5\n10.101.167.164\n10.101.167.173\n10.101.155.44\n10.101.152.72\n10.101.152.101\n10.101.160.135\n10.101.176.123\n10.101.174.187\n10.101.149.1\n10.101.146.174\n10.101.177.26\n10.101.149.34\n10.101.160.177\n10.101.179.93\n10.101.166.111\n10.101.144.204\n10.101.179.106\n10.101.157.226\n10.101.177.204\n10.101.161.216\n10.101.148.250\n10.101.164.62\n10.101.151.107\n10.101.160.141\n10.101.175.16\n10.101.153.239\n10.101.158.198\n10.101.165.130\n10.101.179.104\n10.101.146.169\n10.101.161.132\n10.101.165.97\n10.101.174.38\n10.101.156.50\n10.101.179.167\n10.101.150.226\n10.101.149.245\n10.101.161.39\n10.101.154.230\n10.101.162.186\n10.101.146.29\n10.101.179.45\n10.101.161.65\n10.101.166.183\n10.101.171.203\n10.101.165.4\n10.101.165.231\n10.101.153.164\n10.101.148.255\n10.101.176.119\n10.101.155.113\n10.101.176.70\n10.101.178.112\n10.101.177.126\n10.101.144.84\n10.101.157.4\n10.101.160.78\n10.101.159.66\n10.101.155.59\n10.101.157.184\n10.101.151.245\n10.101.154.118\n10.101.173.101\n10.101.161.134\n10.101.163.135\n10.101.149.121\n10.101.158.117\n10.101.169.115\n10.101.153.110\n10.101.174.50\n10.101.149.69\n10.101.150.249\n10.101.177.113\n10.101.152.36\n10.101.157.108\n10.101.178.213\n10.101.164.207\n10.101.179.76\n10.101.162.44\n10.101.154.241\n10.101.160.109\n10.101.177.253\n10.101.168.149\n10.101.154.35\n10.101.152.209\n10.101.152.203\n10.101.166.160\n10.101.172.22\n10.101.149.68\n10.101.170.104\n10.101.176.87\n10.101.153.137\n10.101.153.184\n10.101.161.228\n10.101.178.69\n10.101.161.213\n10.101.161.88\n10.101.162.250\n10.101.157.20\n10.101.163.78\n10.101.159.187\n10.101.165.204\n10.101.173.181\n10.101.159.98\n10.101.153.69\n10.101.166.155\n10.101.149.23\n10.101.173.170\n10.101.164.97\n10.101.179.242\n10.101.147.233\n10.101.160.193\n10.101.153.102\n10.101.167.12\n10.101.148.134\n10.101.179.178\n10.101.158.65\n10.101.167.13\n10.101.163.248\n10.101.158.249\n10.101.178.239\n10.101.164.196\n10.101.156.239\n10.101.152.167\n10.101.161.37\n10.101.145.84\n10.101.169.30\n10.101.150.225\n10.101.167.219\n10.101.151.8\n10.101.162.229\n10.101.168.182\n10.101.159.73\n10.101.164.75\n10.101.164.227\n10.101.158.74\n10.101.156.193\n10.101.162.35\n10.101.179.80\n10.101.169.232\n10.101.173.149\n10.101.178.122\n10.101.154.229\n10.101.168.103\n10.101.165.45\n10.101.167.202\n10.101.160.248\n10.101.156.127\n10.101.167.94\n10.101.172.11\n10.101.166.144\n10.101.176.216\n10.101.162.231\n10.101.163.36\n10.101.177.106\n10.101.173.114\n10.101.157.142\n10.101.147.199\n10.101.162.135\n10.101.174.86\n10.101.145.11\n10.101.162.199\n10.101.174.37\n10.101.166.188\n10.101.153.211\n10.101.178.52\n10.101.160.110\n10.101.176.14\n10.101.163.111\n10.101.163.204\n10.101.155.134\n10.101.156.97\n10.101.162.2\n10.101.163.199\n10.101.147.248\n10.101.170.85\n10.101.162.28\n10.101.158.166\n10.101.172.177\n10.101.158.207\n10.101.151.182\n10.101.161.133\n10.101.152.206\n10.101.149.166\n10.101.149.160\n10.101.166.231\n10.101.161.70\n10.101.164.40\n10.101.177.150\n10.101.155.50\n10.101.169.153\n10.101.145.219\n10.101.176.60\n10.101.177.68\n10.101.158.25\n10.101.161.62\n10.101.161.215\n10.101.174.147\n10.101.160.235\n10.101.147.142\n10.101.157.36\n10.101.161.79\n172.20.153.83\n172.20.247.19\n172.20.247.19\n172.20.33.35\n172.20.33.35\n10.101.162.81\n10.101.174.138\n10.101.156.227\n10.101.162.67\n10.101.147.23\n10.101.157.55\n10.101.152.107\n10.101.173.78\n10.101.163.42\n10.101.151.177\n10.101.159.134\n10.101.170.1\n10.101.166.150\n10.101.158.159\n10.101.149.71\n10.101.177.191\n10.101.167.38\n10.101.176.213\n10.101.154.247\n10.101.149.24\n10.101.175.192\n10.101.149.19\n10.101.176.248\n10.101.161.147\n10.101.151.98\n10.101.176.154\n10.101.160.159\n10.101.166.115\n10.101.156.117\n10.101.170.209\n10.101.163.145\n10.101.175.216\n10.101.163.51\n10.101.160.49\n10.101.157.238\n10.101.173.145\n10.101.148.233\n10.101.177.147\n10.101.154.20\n10.101.178.181\n10.101.163.57\n10.101.177.88\n10.101.161.230\n10.101.154.94\n10.101.148.29\n10.101.175.243\n10.101.168.20\n10.101.165.28\n10.101.164.167\n10.101.175.37\n10.101.151.59\n10.101.162.101\n10.101.164.147\n10.101.167.105\n10.101.161.48\n10.101.166.209\n10.101.163.45\n10.101.163.90\n10.101.174.132\n10.101.160.49\n10.101.176.213\n10.101.149.125\n10.101.151.16\n10.101.169.215\n10.101.162.237\n10.101.176.192\n10.101.167.160\n10.101.156.107\n10.101.146.145\n10.101.161.9\n10.101.176.68\n10.101.177.143\n10.101.155.2\n10.101.163.203\n10.101.153.29\n10.101.170.194\n10.101.175.140\n10.101.158.68\n10.101.157.135\n10.101.164.111\n10.101.158.252\n10.101.170.149\n10.101.176.193\n10.101.179.24\n10.101.174.73\n10.101.155.130\n10.101.152.252\n10.101.163.123\n10.101.158.184\n10.101.149.133\n10.101.149.41\n10.101.167.226\n10.101.166.85\n10.101.148.246\n10.101.158.170\n10.101.153.130\n10.101.173.145\n10.101.173.213\n10.101.162.69\n10.101.170.5\n10.101.159.76\n10.101.155.238\n10.101.152.91\n10.101.166.157\n10.101.176.101\n10.101.170.229\n10.101.164.111\n10.101.146.235\n10.101.178.81\n10.101.167.29\n10.101.157.176\n10.101.157.176\n10.101.162.67\n10.101.145.164\n10.101.168.28\n10.101.169.116\n10.101.175.126\n10.101.176.91\n10.101.156.171\n10.101.154.33\n10.101.178.193\n10.101.146.163\n10.101.160.83\n10.101.178.28\n10.101.178.209\n10.101.164.244\n10.101.168.195\n10.101.157.172\n10.101.159.129\n10.101.161.142\n10.101.160.126\n10.101.153.125\n10.101.154.71\n10.101.169.215\n10.101.149.125\n10.101.150.97\n10.101.155.64\n10.101.159.123\n10.101.159.69\n10.101.177.183\n10.101.173.117\n10.101.174.111\n10.101.167.156\n10.101.161.240\n10.101.156.227\n10.101.177.114\n10.101.166.52\n10.101.167.57\n10.101.177.180\n10.101.164.154\n10.101.148.180\n10.101.166.99\n10.101.161.152\n10.101.173.156\n10.101.163.2\n10.101.161.83\n10.101.149.41\n10.101.154.177\n10.101.154.71\n10.101.177.194\n10.101.152.215\n10.101.165.138\n10.101.177.6\n10.101.159.247\n10.101.179.20\n10.101.174.195\n10.101.177.183\n10.101.164.204\n10.101.161.255\n10.101.176.57\n10.101.155.201\n10.101.164.245\n10.101.175.140\n10.101.179.97\n10.101.159.194\n10.101.177.191\n10.101.176.153\n10.101.177.232\n10.101.163.148\n10.101.164.241\n10.101.177.151\n10.101.164.36\n10.101.158.143\n10.101.163.225\n10.101.166.225\n10.101.175.110\n10.101.165.149\n10.101.163.253\n10.101.173.72\n10.101.161.136\n10.101.163.49\n10.101.154.145\n10.101.166.225\n10.101.147.49\n10.101.160.215\n10.101.176.53\n10.101.161.116\n10.101.153.46\n10.101.165.28\n10.101.163.54\n10.101.177.214\n10.101.163.2\n10.101.166.229\n10.101.160.74\n10.101.157.181\n10.101.175.216\n10.101.174.138\n10.101.166.245\n10.101.163.79\n10.101.146.163\n10.101.176.208\n10.101.160.37\n10.101.158.56\n10.101.163.182\n10.101.175.126\n10.101.176.192\n10.101.148.125\n10.101.168.195\n10.101.166.7\n10.101.165.159\n10.101.155.129\n10.101.159.211\n10.101.177.78\n10.101.176.190\n10.101.144.62\n10.101.147.14\n10.101.162.76\n10.101.151.222\n10.101.179.51\n10.101.178.196\n10.101.152.216\n10.101.178.247\n10.101.152.14\n10.101.160.132\n10.101.155.87\n10.101.164.101\n10.101.147.190\n10.101.161.153\n10.101.160.83\n10.101.154.20\n10.101.158.225\n10.101.144.10\n10.101.160.162\n10.101.162.222\n10.101.161.152\n10.101.166.196\n10.101.177.180\n10.101.166.99\n10.101.176.228\n10.101.156.28\n10.101.155.105\n10.101.150.193\n10.101.165.70\n10.101.165.224\n10.101.159.81\n10.101.149.109\n10.101.153.237\n10.101.145.234\n10.101.161.153\n10.101.154.209\n10.101.157.88\n10.101.179.238\n10.101.155.73\n10.101.162.236\n10.101.164.243\n10.101.161.81\n10.101.164.154\n10.101.172.33\n10.101.162.222\n10.101.161.87\n10.101.168.20\n10.101.161.123\n10.101.172.141\n10.101.167.101\n10.101.160.62\n10.101.173.156\n10.101.163.12\n10.101.154.75\n10.101.178.209\n10.101.156.171\n10.101.177.115\n10.101.169.79\n10.101.164.132\n10.101.171.175\n10.101.153.175\n10.101.163.123\n10.101.146.74\n10.101.150.97\n10.101.164.17\n10.101.148.221\n10.101.165.149\n10.101.179.147\n10.101.165.223\n10.101.158.10\n10.101.146.183\n10.101.163.30\n10.101.162.10\n10.101.155.121\n10.101.155.2\n10.101.176.61\n10.101.177.143\n10.101.179.77\n10.101.150.150\n10.101.161.102\n10.101.163.117\n10.101.165.159\n10.101.165.39\n10.101.167.157\n10.101.156.93\n10.101.175.190\n10.101.152.21\n10.101.178.16\n10.101.149.24\n10.101.147.189\n10.101.176.17\n10.101.177.164\n10.101.179.24\n10.101.148.180\n10.101.153.207\n10.101.161.171\n10.101.178.0\n10.101.155.105\n10.101.149.232\n10.101.162.132\n10.101.163.20\n10.101.173.247\n10.101.179.185\n10.101.152.14\n10.101.147.156\n10.101.153.139\n10.101.157.119\n10.101.171.245\n10.101.159.29\n10.101.147.225\n10.101.176.125\n10.101.152.112\n10.101.177.122\n10.101.160.158\n10.101.163.21\n10.101.151.196\n10.101.154.7\n10.101.153.175\n10.101.161.81\n10.101.160.205\n10.101.178.47\n10.101.170.208\n10.101.149.6\n10.101.153.133\n10.101.166.199\n10.101.169.238\n10.101.163.66\n10.101.160.91\n10.101.162.237\n10.101.162.112\n10.101.177.55\n10.101.160.107\n10.101.179.144\n10.101.165.200\n10.101.162.182\n10.101.148.232\n10.101.159.162\n10.101.161.48\n10.101.177.115\n10.101.176.117\n10.101.146.183\n10.101.147.14\n10.101.157.54\n10.101.161.158\n10.101.161.136\n10.101.175.110\n10.101.176.38\n10.101.160.126\n10.101.163.0\n10.101.160.238\n10.101.148.150\n10.101.168.79\n10.101.167.50\n10.101.163.253\n10.101.164.161\n10.101.160.162\n10.101.158.194\n10.101.158.115\n10.101.145.165\n10.101.175.230\n10.101.166.85\n10.101.159.209\n10.101.166.199\n10.101.174.62\n10.101.160.38\n10.101.159.77\n10.101.160.153\n10.101.159.76\n10.101.157.181\n10.101.146.243\n10.101.177.35\n10.101.165.182\n10.101.176.58\n10.101.153.55\n10.101.160.37\n10.101.148.181\n10.101.164.101\n10.101.166.77\n10.101.177.27\n10.101.177.242\n10.101.161.74\n10.101.163.211\n10.101.160.152\n10.101.155.210\n10.101.177.32\n10.101.175.104\n10.101.162.7\n10.101.179.65\n10.101.164.183\n10.101.176.154\n10.101.154.69\n10.101.156.107\n10.101.160.147\n10.101.165.155\n10.101.160.81\n10.101.151.22\n10.101.170.75\n10.101.159.168\n10.101.152.212\n10.101.178.156\n10.101.163.132\n10.101.157.219\n10.101.158.184\n10.101.177.88\n10.101.159.29\n10.101.161.74\n10.101.162.14\n10.101.176.45\n10.101.154.81\n10.101.166.218\n10.101.175.151\n10.101.176.176\n10.101.172.230\n10.101.173.229\n10.101.176.17\n10.101.161.197\n10.101.179.97\n10.101.162.75\n10.101.156.28\n10.101.154.81\n10.101.178.28\n10.101.156.78\n10.101.176.25\n10.101.162.182\n10.101.178.155\n10.101.158.159\n10.101.176.191\n10.101.177.199\n10.101.163.134\n10.101.168.79\n10.101.164.140\n10.101.167.162\n10.101.167.50\n10.101.166.245\n10.101.167.47\n10.101.162.111\n10.101.150.150\n10.101.154.19\n10.101.165.155\n10.101.157.22\n10.101.165.30\n10.101.163.3\n10.101.164.161\n10.101.166.148\n10.101.166.7\n10.101.151.98\n10.101.157.172\n10.101.167.243\n10.101.146.191\n10.101.164.205\n10.101.152.173\n10.101.176.45\n10.101.147.45\n10.101.163.79\n10.101.153.237\n10.101.159.162\n10.101.167.33\n10.101.152.126\n10.101.161.192\n10.101.167.38\n10.101.167.123\n10.101.149.32\n10.101.179.162\n10.101.148.181\n10.101.147.56\n10.101.162.107\n10.101.166.5\n10.101.165.17\n10.101.176.103\n10.101.164.183\n10.101.160.153\n10.101.179.162\n10.101.158.218\n10.101.163.3\n10.101.176.163\n10.101.161.96\n10.101.161.15\n10.101.176.194\n10.101.153.144\n10.101.152.173\n10.101.156.117\n10.101.178.16\n10.101.158.252\n10.101.152.48\n10.101.166.33\n10.101.174.195\n10.101.155.117\n10.101.160.74\n10.101.159.141\n10.101.149.198\n10.101.162.70\n10.101.173.189\n10.101.163.147\n10.101.146.242\n10.101.169.207\n10.101.165.7\n10.101.150.151\n10.101.163.120\n10.101.151.22\n10.101.153.133\n10.101.163.245\n10.101.151.16\n10.101.167.204\n10.101.165.127\n10.101.155.243\n10.101.176.163\n10.101.165.138\n10.101.176.48\n10.101.174.43\n10.101.146.235\n10.101.176.91\n10.101.164.241\n10.101.164.98\n10.101.151.231\n10.101.176.194\n10.101.156.93\n10.101.165.119\n10.101.156.139\n10.101.155.238\n10.101.165.160\n10.101.152.48\n10.101.160.71\n10.101.167.92\n10.101.162.98\n10.101.173.72\n10.101.149.32\n10.101.178.225\n10.101.159.209\n10.101.162.14\n10.101.173.246\n10.101.163.120\n10.101.164.173\n10.101.156.194\n10.101.170.1\n10.101.167.101\n10.101.163.245\n10.101.153.240\n10.101.154.204\n10.101.148.132\n10.101.155.130\n10.101.166.82\n10.101.177.114\n10.101.179.20\n10.101.154.80\n10.101.155.85\n10.101.152.35\n10.101.159.141\n10.101.149.94\n10.101.175.243\n10.101.146.55\n10.101.154.177\n10.101.148.233\n10.101.179.238\n10.101.161.176\n10.101.151.33\n10.101.153.207\n10.101.164.252\n10.101.144.214\n10.101.159.56\n10.101.148.125\n10.101.161.209\n10.101.156.187\n10.101.159.77\n10.101.154.26\n10.101.164.70\n10.101.170.75\n10.101.167.123\n10.101.158.115\n10.101.176.58\n10.101.177.243\n10.101.151.59\n10.101.178.196\n10.101.148.221\n10.101.152.212\n10.101.179.65\n10.101.151.201\n10.101.179.110\n10.101.149.96\n10.101.148.232\n10.101.161.9\n10.101.153.130\n10.101.162.110\n10.101.172.70\n10.101.154.125\n10.101.157.2\n10.101.153.29\n10.101.178.231\n10.101.145.165\n10.101.166.115\n10.101.151.197\n10.101.167.160\n10.101.172.81\n10.101.147.45\n10.101.163.90\n10.101.157.140\n10.101.144.214\n10.101.169.238\n10.101.174.160\n10.101.167.105\n10.101.179.144\n10.101.176.190\n10.101.146.189\n10.101.163.246\n10.101.167.233\n10.101.164.98\n10.101.166.157\n10.101.146.224\n10.101.147.8\n10.101.158.170\n10.101.146.16\n10.101.148.66\n10.101.170.209\n10.101.169.23\n10.101.178.156\n10.101.162.203\n10.101.178.73\n10.101.179.185\n10.101.162.70\n10.101.160.71\n10.101.174.5\n10.101.177.58\n10.101.152.252\n10.101.171.245\n10.101.176.117\n10.101.173.246\n10.101.159.111\n10.101.170.201\n10.101.163.20\n10.101.165.143\n10.101.162.98\n10.101.162.10\n10.101.162.1\n10.101.161.102\n10.101.155.164\n10.101.156.139\n10.101.172.215\n10.101.163.254\n10.101.160.45\n10.101.176.222\n10.101.152.215\n10.101.161.193\n10.101.154.94\n10.101.161.209\n10.101.177.55\n10.101.149.96\n10.101.148.210\n10.101.157.54\n10.101.176.180\n10.101.165.223\n10.101.167.92\n10.101.153.46\n10.101.169.23\n10.101.166.148\n10.101.150.158\n10.101.172.70\n10.101.151.127\n10.101.160.62\n10.101.149.36\n10.101.167.91\n10.101.161.240\n10.101.151.222\n10.101.161.87\n10.101.177.199\n10.101.158.116\n10.101.176.55\n10.101.176.193\n10.101.147.23\n10.101.175.149\n10.101.157.88\n10.101.161.230\n10.101.158.116\n10.101.158.68\n10.101.154.42\n10.101.158.56\n10.101.162.7\n10.101.176.247\n10.101.147.206\n10.101.172.141\n10.101.163.117\n10.101.169.109\n10.101.174.132\n10.101.157.237\n10.101.160.207\n10.101.149.94\n10.101.146.243\n10.101.161.234\n10.101.175.104\n10.101.155.131\n10.101.156.173\n10.101.164.243\n10.101.176.118\n10.101.152.35\n10.101.151.150\n10.101.145.234\n10.101.158.189\n10.101.178.193\n10.101.147.56\n10.101.172.229\n10.101.163.57\n10.101.154.189\n10.101.176.53\n10.101.153.240\n10.101.158.143\n10.101.154.145\n10.101.161.158\n10.101.176.169\n10.101.155.118\n10.101.164.5\n10.101.166.82\n10.101.159.70\n10.101.161.123\n10.101.155.243\n10.101.177.198\n10.101.173.215\n10.101.161.128\n10.101.154.26\n10.101.174.73\n10.101.152.238\n10.101.173.117\n10.101.158.95\n10.101.171.175\n10.101.155.87\n10.101.160.204\n10.101.165.188\n10.101.165.88\n10.101.164.224\n10.101.158.10\n10.101.174.111\n10.101.162.81\n10.101.167.8\n10.101.157.237\n10.101.165.94\n10.101.163.42\n10.101.161.15\n10.101.166.198\n10.101.176.57\n10.101.151.88\n10.101.167.47\n10.101.163.215\n10.101.155.210\n10.101.176.61\n10.101.160.159\n10.101.163.215\n10.101.164.224\n10.101.151.33\n10.101.160.215\n10.101.165.182\n10.101.165.30\n10.101.170.217\n10.101.148.210\n10.101.161.202\n10.101.153.85\n10.101.166.10\n10.101.154.189\n10.101.144.10\n10.101.164.132\n10.101.167.249\n10.101.167.143\n10.101.147.189\n10.101.176.247\n10.101.161.176\n10.101.173.33\n10.101.177.232\n10.101.152.126\n10.101.161.128\n10.101.146.239\n10.101.168.194\n10.101.165.17\n10.101.153.139\n10.101.151.88\n10.101.163.254\n10.101.176.101\n10.101.176.38\n10.101.153.166\n10.101.175.149\n10.101.153.193\n10.101.164.205\n10.101.146.224\n10.101.157.119\n10.101.154.42\n10.101.150.54\n10.101.169.207\n10.101.156.103\n10.101.148.67\n10.101.176.208\n10.101.155.12\n10.101.164.15\n10.101.148.246\n10.101.178.124\n10.101.166.229\n10.101.167.91\n10.101.147.190\n10.101.177.242\n10.101.179.217\n10.101.160.81\n10.101.153.55\n10.101.166.129\n10.101.148.29\n10.101.174.43\n10.101.167.8\n10.101.164.204\n10.101.158.226\n10.101.177.172\n10.101.164.130\n10.101.161.142\n10.101.167.29\n10.101.160.199\n10.101.150.100\n10.101.155.144\n10.101.155.35\n10.101.155.35\n10.101.155.144\n10.101.153.45\n10.101.152.221\n10.101.153.4\n10.101.154.111\n10.101.152.33\n10.101.153.88\n10.101.152.246\n10.101.152.246\n10.101.153.71\n10.101.153.71\n10.101.152.82\n10.101.153.104\n10.101.155.6\n10.101.148.63\n10.101.148.63\n10.101.153.212\n10.101.148.228\n10.101.151.106\n2001:db8:3333:4444:5555:6666:7777:8888\n2001:db8:3333:4444:5555:6666:7777:8889\n2001:db8:3333:4444:5555:6666:7777:888a\n2001:db8:3333:4444:5555:6666:77ff:0001\n2001:db8:3333:4444:5555:6666:77ff:0002\n2001:db8:3333:ab32:0000:0000:0000:0001\n2001:db8:3333:ab32:0000:0000:0000:0002\n2001:db8:3333:ab32:0001:0000:0000:0001\n2001:db8:3333:ab32:0001:0000:0000:0002"
  },
  {
    "path": "pkg/tmconfig/admin_command.go",
    "content": "package tmconfig\n\nimport (\n\t\"github.com/go-json-experiment/json\"\n\t\"sigs.k8s.io/yaml\"\n)\n\ntype CommandName string\n\nconst (\n\tRemoveIntercept CommandName = \"removeIntercept\"\n)\n\ntype AdminCommand struct {\n\tName CommandName `json:\"name\"`\n\tArgs []string    `json:\"args\"`\n\n\t// Timestamp expressed as nanoseconds since the Unix epoch.\n\tTimestamp int64 `json:\"timestamp\"`\n}\n\ntype AdminCommandList []AdminCommand\n\nfunc (l AdminCommandList) MarshalYAML() ([]byte, error) {\n\tdata, err := json.Marshal(l)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn yaml.JSONToYAML(data)\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (l *AdminCommandList) UnmarshalYAML(data []byte) error {\n\tdata, err := yaml.YAMLToJSON(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn json.Unmarshal(data, l)\n}\n"
  },
  {
    "path": "pkg/tmconfig/configmap.go",
    "content": "package tmconfig\n\nimport (\n\t\"context\"\n\t\"slices\"\n\n\tcore \"k8s.io/api/core/v1\"\n\tmeta \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/util/retry\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/agentconfig\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\nconst (\n\tClientConfigFileName            = \"client.yaml\"\n\tAgentEnvConfigFileName          = \"agent-env.yaml\"\n\tAdminCommandsFileName           = \"admin-commands.yaml\"\n\tNamespaceSelectorConfigFileName = \"namespace-selector.yaml\"\n\tAgentStateFileName              = \"agent-state.yaml\"\n\tCfgConfigMapName                = agentconfig.ManagerAppName\n)\n\nfunc ReadConfig(ctx context.Context, namespace string) (*core.ConfigMap, error) {\n\tapi := k8sapi.GetK8sInterface(ctx).CoreV1()\n\treturn api.ConfigMaps(namespace).Get(ctx, CfgConfigMapName, meta.GetOptions{})\n}\n\nfunc UpdateConfig(ctx context.Context, cm *core.ConfigMap) (*core.ConfigMap, error) {\n\tapi := k8sapi.GetK8sInterface(ctx).CoreV1()\n\treturn api.ConfigMaps(cm.Namespace).Update(ctx, cm, meta.UpdateOptions{})\n}\n\n// AddCommand adds a command to the configmap.\nfunc AddCommand(ctx context.Context, namespace string, command AdminCommand) error {\n\treturn retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\tcm, err := ReadConfig(ctx, namespace)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata := []byte(cm.Data[AdminCommandsFileName])\n\t\tcommands := make(AdminCommandList, 0, 1+len(data))\n\t\tif len(data) > 0 {\n\t\t\terr = commands.UnmarshalYAML(data)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\tcommands = append(commands, command)\n\t\tdata, err = commands.MarshalYAML()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif cm.Data == nil {\n\t\t\tcm.Data = make(map[string]string)\n\t\t}\n\t\tcm.Data[AdminCommandsFileName] = string(data)\n\t\t_, err = UpdateConfig(ctx, cm)\n\t\treturn err\n\t})\n}\n\n// ClearCommands removes all commands older than, or equal to, the given time.\nfunc ClearCommands(ctx context.Context, namespace string, olderThan int64) error {\n\treturn retry.RetryOnConflict(retry.DefaultRetry, func() error {\n\t\tcm, err := ReadConfig(ctx, namespace)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdata := []byte(cm.Data[AdminCommandsFileName])\n\t\tif len(data) == 0 {\n\t\t\treturn nil\n\t\t}\n\t\tvar commands AdminCommandList\n\t\terr = commands.UnmarshalYAML(data)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tszBefore := len(commands)\n\t\tcommands = slices.DeleteFunc(commands, func(command AdminCommand) bool { return command.Timestamp <= olderThan })\n\t\tif len(commands) == szBefore {\n\t\t\treturn nil\n\t\t}\n\t\tdata, err = commands.MarshalYAML()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tcm.Data[AdminCommandsFileName] = string(data)\n\t\t_, err = UpdateConfig(ctx, cm)\n\t\treturn err\n\t})\n}\n"
  },
  {
    "path": "pkg/tunnel/client_stream.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n)\n\ntype GRPCClientStream interface {\n\tGRPCStream\n\tCloseSend() error\n}\n\nfunc NewClientStream(ctx context.Context, tag Tag, grpcStream GRPCClientStream, id ConnID, sessionID SessionID, callDelay, dialTimeout time.Duration) (Stream, error) {\n\ts := &clientStream{stream: newStream(tag, grpcStream)}\n\ts.id = id\n\ts.roundtripLatency = callDelay\n\ts.dialTimeout = dialTimeout\n\ts.sessionID = sessionID\n\n\tif err := s.Send(ctx, StreamInfoMessage(id, sessionID, callDelay, dialTimeout)); err != nil {\n\t\t_ = s.CloseSend(ctx)\n\t\treturn nil, err\n\t}\n\tm, err := s.Receive(ctx)\n\tif err != nil {\n\t\t_ = s.CloseSend(ctx)\n\t\treturn nil, fmt.Errorf(\"failed to read initial StreamOK message: %w\", err)\n\t}\n\tif m.Code() != streamOK {\n\t\t_ = s.CloseSend(ctx)\n\t\treturn nil, errors.New(\"initial message was not StreamOK\")\n\t}\n\ts.peerVersion = getVersion(m)\n\treturn s, nil\n}\n\ntype clientStream struct {\n\tstream\n}\n\nfunc (s *clientStream) CloseSend(_ context.Context) error {\n\treturn s.grpcStream.(GRPCClientStream).CloseSend()\n}\n"
  },
  {
    "path": "pkg/tunnel/connid.go",
    "content": "package tunnel\n\nimport (\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// A ConnID is a compact and immutable representation of protocol, source IP, source port, destination IP and destination port which\n// is suitable as a map key.\ntype ConnID string\n\nfunc ConnIDFromUDP(src, dst netip.AddrPort) ConnID {\n\treturn NewConnID(types.ProtoUDP, src, dst)\n}\n\n// NewConnID returns a new ConnID for the given values.\nfunc NewConnID(proto types.Proto, src, dst netip.AddrPort) ConnID {\n\tsrcAddr := src.Addr()\n\tdstAddr := dst.Addr()\n\tswitch {\n\tcase srcAddr.Is4():\n\t\tif dstAddr.Is4In6() {\n\t\t\tdstAddr = dstAddr.Unmap()\n\t\t} else if dstAddr.Is6() {\n\t\t\tsrcAddr = netip.AddrFrom16(srcAddr.As16())\n\t\t}\n\tcase srcAddr.Is4In6():\n\t\tif dstAddr.Is4() {\n\t\t\tsrcAddr = srcAddr.Unmap()\n\t\t} else if dstAddr.Is4In6() {\n\t\t\tsrcAddr = srcAddr.Unmap()\n\t\t\tdstAddr = dstAddr.Unmap()\n\t\t}\n\tdefault:\n\t\tif dstAddr.Is4() {\n\t\t\tdstAddr = netip.AddrFrom16(dstAddr.As16())\n\t\t}\n\t}\n\n\tls := srcAddr.BitLen() / 8\n\tld := dstAddr.BitLen() / 8\n\tif ls == 0 {\n\t\tpanic(\"invalid source IP\")\n\t}\n\tif ld == 0 {\n\t\tpanic(\"invalid destination IP\")\n\t}\n\tbs := make([]byte, ls+ld+5)\n\tcopy(bs, srcAddr.AsSlice())\n\tbinary.BigEndian.PutUint16(bs[ls:], src.Port())\n\tls += 2\n\tcopy(bs[ls:], dstAddr.AsSlice())\n\tls += ld\n\tbinary.BigEndian.PutUint16(bs[ls:], dst.Port())\n\tls += 2\n\tbs[ls] = byte(proto)\n\treturn ConnID(bs)\n}\n\nfunc NewZeroID() ConnID {\n\treturn ConnID(make([]byte, 13))\n}\n\n// areBothIPv4 returns true if the source and destination of this ConnID are both IPv4.\nfunc (id ConnID) areBothIPv4() bool {\n\treturn len(id) == 13\n}\n\n// IsSourceIPv4 returns true if the source of this ConnID is IPv4.\nfunc (id ConnID) IsSourceIPv4() bool {\n\treturn id.areBothIPv4() || len(id) > 16 && net.IP(id[0:16]).To4() != nil\n}\n\n// IsDestinationIPv4 returns true if the destination of this ConnID is IPv4.\nfunc (id ConnID) IsDestinationIPv4() bool {\n\treturn id.areBothIPv4() || len(id) == 37 && net.IP(id[18:34]).To4() != nil\n}\n\n// Source returns the source address and port.\nfunc (id ConnID) Source() netip.AddrPort {\n\treturn netip.AddrPortFrom(id.SourceAddr(), id.SourcePort())\n}\n\n// SourceAddr returns the source IP.\nfunc (id ConnID) SourceAddr() netip.Addr {\n\tb := []byte(id)\n\tif id.areBothIPv4() {\n\t\treturn netip.AddrFrom4(*(*[4]byte)(b[0:4]))\n\t}\n\treturn netip.AddrFrom16(*(*[16]byte)(b[0:16])).Unmap()\n}\n\n// SourcePort returns the source port.\nfunc (id ConnID) SourcePort() uint16 {\n\tif id.areBothIPv4() {\n\t\treturn binary.BigEndian.Uint16([]byte(id)[4:])\n\t}\n\treturn binary.BigEndian.Uint16([]byte(id)[16:])\n}\n\n// Destination returns the destination address and port.\nfunc (id ConnID) Destination() netip.AddrPort {\n\treturn netip.AddrPortFrom(id.DestinationAddr(), id.DestinationPort())\n}\n\n// DestinationAddr returns the destination IP.\nfunc (id ConnID) DestinationAddr() netip.Addr {\n\tb := []byte(id)\n\tif id.areBothIPv4() {\n\t\treturn netip.AddrFrom4(*(*[4]byte)(b[6:10]))\n\t}\n\treturn netip.AddrFrom16(*(*[16]byte)(b[18:34])).Unmap()\n}\n\n// DestinationPort returns the destination port.\nfunc (id ConnID) DestinationPort() uint16 {\n\tif id.areBothIPv4() {\n\t\treturn binary.BigEndian.Uint16([]byte(id)[10:])\n\t}\n\treturn binary.BigEndian.Uint16([]byte(id)[34:])\n}\n\n// Protocol returns the protocol, e.g. ipproto.TCP.\nfunc (id ConnID) Protocol() types.Proto {\n\treturn types.Proto(id[len(id)-1])\n}\n\n// SourceProtocolString returns the protocol string for the source, e.g. \"tcp4\".\nfunc (id ConnID) SourceProtocolString() (proto string) {\n\tp := id.Protocol()\n\tswitch p {\n\tcase types.ProtoTCP:\n\t\tif id.IsSourceIPv4() {\n\t\t\tproto = \"tcp4\"\n\t\t} else {\n\t\t\tproto = \"tcp6\"\n\t\t}\n\tcase types.ProtoUDP:\n\t\tif id.IsSourceIPv4() {\n\t\t\tproto = \"udp4\"\n\t\t} else {\n\t\t\tproto = \"udp6\"\n\t\t}\n\tdefault:\n\t\tproto = fmt.Sprintf(\"unknown-%d\", p)\n\t}\n\treturn proto\n}\n\n// DestinationProtocolString returns the protocol string for the source, e.g. \"tcp4\".\nfunc (id ConnID) DestinationProtocolString() (proto string) {\n\tp := id.Protocol()\n\tswitch p {\n\tcase types.ProtoTCP:\n\t\tif id.IsDestinationIPv4() {\n\t\t\tproto = \"tcp4\"\n\t\t} else {\n\t\t\tproto = \"tcp6\"\n\t\t}\n\tcase types.ProtoUDP:\n\t\tif id.IsDestinationIPv4() {\n\t\t\tproto = \"udp4\"\n\t\t} else {\n\t\t\tproto = \"udp6\"\n\t\t}\n\tdefault:\n\t\tproto = fmt.Sprintf(\"unknown-%d\", p)\n\t}\n\treturn proto\n}\n\n// SourceNetwork returns either \"ip4\" or \"ip6\".\nfunc (id ConnID) SourceNetwork() string {\n\tif id.IsSourceIPv4() {\n\t\treturn \"ip4\"\n\t}\n\treturn \"ip6\"\n}\n\n// DestinationNetwork returns either \"ip4\" or \"ip6\".\nfunc (id ConnID) DestinationNetwork() string {\n\tif id.IsDestinationIPv4() {\n\t\treturn \"ip4\"\n\t}\n\treturn \"ip6\"\n}\n\n// Reply returns a copy of this ConnID with swapped source and destination properties.\nfunc (id ConnID) Reply() ConnID {\n\treturn NewConnID(id.Protocol(), id.Destination(), id.Source())\n}\n\n// ReplyString returns a formatted string suitable for logging showing the destination:destinationPort -> source:sourcePort.\nfunc (id ConnID) ReplyString() string {\n\treturn fmt.Sprintf(\"%s %s -> %s\",\n\t\tid.Protocol(), id.Destination(), id.Source())\n}\n\n// String returns a formatted string suitable for logging showing the source:sourcePort -> destination:destinationPort.\nfunc (id ConnID) String() string {\n\tif len(id) < 13 {\n\t\treturn \"bogus ConnID\"\n\t}\n\treturn fmt.Sprintf(\"%s %s -> %s\",\n\t\tid.Protocol(), id.Source(), id.Destination())\n}\n"
  },
  {
    "path": "pkg/tunnel/connid_test.go",
    "content": "package tunnel\n\nimport (\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nvar (\n\tipv4a     = netip.MustParseAddr(\"192.168.0.1\")\n\tipv4b     = netip.MustParseAddr(\"192.168.3.8\")\n\tipv6a     = netip.MustParseAddr(\"2a05:d014:153d:d732:a2d3::15\")\n\tipv6b     = netip.MustParseAddr(\"2a05:d014:153d:d732:a2f2::08\")\n\tipv4Asv6a = netip.AddrFrom16(ipv4a.As16())\n\tipv4Asv6b = netip.AddrFrom16(ipv4b.As16())\n)\n\nfunc TestConnIDFromUDP(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tsrc  netip.AddrPort\n\t\tdst  netip.AddrPort\n\t\twant string\n\t}{\n\t\t{\n\t\t\tname: \"ipv4-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: \"UDP 192.168.0.1:4 -> 192.168.3.8:8\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: \"UDP 192.168.0.1:4 -> [2a05:d014:153d:d732:a2f2::8]:8\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: \"UDP 192.168.0.1:4 -> 192.168.3.8:8\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: \"UDP [2a05:d014:153d:d732:a2d3::15]:4 -> 192.168.3.8:8\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: \"UDP [2a05:d014:153d:d732:a2d3::15]:4 -> [2a05:d014:153d:d732:a2f2::8]:8\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: \"UDP [2a05:d014:153d:d732:a2d3::15]:4 -> 192.168.3.8:8\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: \"UDP 192.168.0.1:4 -> 192.168.3.8:8\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: \"UDP 192.168.0.1:4 -> [2a05:d014:153d:d732:a2f2::8]:8\",\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: \"UDP 192.168.0.1:4 -> 192.168.3.8:8\",\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, tt.want, ConnIDFromUDP(tt.src, tt.dst).String(), \"ConnIDFromUDP(%v, %v)\", tt.src, tt.dst)\n\t\t})\n\t}\n}\n\nfunc TestConnID_Source(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tsrc  netip.AddrPort\n\t\tdst  netip.AddrPort\n\t\twant netip.AddrPort\n\t}{\n\t\t{\n\t\t\tname: \"ipv4-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4a, 4),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4a, 4),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4a, 4),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv6a, 4),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv6a, 4),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv6a, 4),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4a, 4),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4a, 4),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4a, 4),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, tt.want, NewConnID(types.ProtoTCP, tt.src, tt.dst).Source(), \"Source()\")\n\t\t})\n\t}\n}\n\nfunc TestConnID_Destination(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tsrc  netip.AddrPort\n\t\tdst  netip.AddrPort\n\t\twant netip.AddrPort\n\t}{\n\t\t{\n\t\t\tname: \"ipv4-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4b, 8),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv6b, 8),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4b, 8),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4b, 8),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv6b, 8),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4b, 8),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4b, 8),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv6b, 8),\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: netip.AddrPortFrom(ipv4b, 8),\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, tt.want, NewConnID(types.ProtoTCP, tt.src, tt.dst).Destination(), \"Source()\")\n\t\t})\n\t}\n}\n\nfunc TestConnID_areBothIPv4(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tsrc  netip.AddrPort\n\t\tdst  netip.AddrPort\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"ipv4-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ipv6-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv4\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4b, 8),\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv6b, 8),\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ipv4as6-ipv4as6\",\n\t\t\tsrc:  netip.AddrPortFrom(ipv4Asv6a, 4),\n\t\t\tdst:  netip.AddrPortFrom(ipv4Asv6b, 8),\n\t\t\twant: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tassert.Equalf(t, tt.want, ConnIDFromUDP(tt.src, tt.dst).areBothIPv4(), \"ConnIDFromUDP(%v, %v)\", tt.src, tt.dst)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/tunnel/context.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n)\n\ntype poolKey struct{}\n\n// WithPool returns a context with the given Pool.\nfunc WithPool(ctx context.Context, pool *Pool) context.Context {\n\treturn context.WithValue(ctx, poolKey{}, pool)\n}\n\nfunc GetPool(ctx context.Context) *Pool {\n\tpool, ok := ctx.Value(poolKey{}).(*Pool)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn pool\n}\n\ntype synIpRsvKey struct{}\n\nfunc WithSyntheticIPResolver(ctx context.Context, r SyntheticIPResolver) context.Context {\n\treturn context.WithValue(ctx, synIpRsvKey{}, r)\n}\n\nfunc GetSyntheticIPResolver(ctx context.Context) SyntheticIPResolver {\n\tif r, ok := ctx.Value(synIpRsvKey{}).(SyntheticIPResolver); ok {\n\t\treturn r\n\t}\n\treturn noopSyntheticIPResolver{}\n}\n\ntype noopSyntheticIPResolver struct{}\n\nfunc (noopSyntheticIPResolver) Resolve(addr netip.Addr) (netip.Addr, error) {\n\treturn addr, nil\n}\n\nfunc (noopSyntheticIPResolver) ResolveName(netip.Addr) string {\n\treturn \"\"\n}\n\ntype dialerKey struct{}\n\nfunc WithDialer(ctx context.Context, d Dialer) context.Context {\n\treturn context.WithValue(ctx, dialerKey{}, d)\n}\n\nfunc GetDialer(ctx context.Context) Dialer {\n\tif d, ok := ctx.Value(dialerKey{}).(Dialer); ok {\n\t\treturn d\n\t}\n\treturn DefaultDialer{}\n}\n\ntype DefaultDialer struct{}\n\nfunc (n DefaultDialer) DialTCP(ctx context.Context, ap netip.AddrPort) (conn net.Conn, err error) {\n\td := net.Dialer{}\n\treturn d.DialContext(ctx, \"tcp\", ap.String())\n}\n\nfunc (n DefaultDialer) DialUDP(ctx context.Context, localAddr netip.AddrPort, remoteAddr netip.AddrPort) (conn net.Conn, err error) {\n\td := net.Dialer{}\n\tif !localAddr.IsValid() {\n\t\treturn d.DialContext(ctx, \"udp\", remoteAddr.String())\n\t}\n\tlua := net.UDPAddrFromAddrPort(localAddr)\n\trua := net.UDPAddrFromAddrPort(remoteAddr)\n\treturn net.DialUDP(\"udp\", lua, rua)\n}\n"
  },
  {
    "path": "pkg/tunnel/dialer.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/agent\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// The idleDuration controls how long a dialer for a specific proto+from-to address combination remains alive without\n// reading or writing any messages. The dialer is normally closed by one of the peers.\nconst (\n\ttcpConnTTL       = 2 * time.Hour // Default tcp_keepalive_time on Linux\n\tudpConnTTL       = 2 * time.Second\n\tlocalDialTimeout = 2 * time.Second\n)\n\nconst (\n\tnotConnected = int32(iota)\n\tconnecting\n\tconnected\n\treadClosed\n\twriteClosed\n)\n\ntype Dialer interface {\n\tDialTCP(context.Context, netip.AddrPort) (conn net.Conn, err error)\n\tDialUDP(context.Context, netip.AddrPort, netip.AddrPort) (conn net.Conn, err error)\n}\n\ntype halfReadCloser interface {\n\tCloseRead() error\n}\n\ntype halfWriteCloser interface {\n\tCloseWrite() error\n}\n\ntype halfCloser interface {\n\thalfReadCloser\n\thalfWriteCloser\n}\n\n// streamReader is implemented by the dialer and udpListener so that they can share the\n// readLoop function.\ntype streamReader interface {\n\tIdle() <-chan time.Time\n\tResetIdle() bool\n\tStop(context.Context)\n\tgetStream() Stream\n\treply([]byte) (int, error)\n\tstartDisconnect(context.Context, string, bool)\n}\n\n// The dialer takes care of dispatching messages between gRPC and UDP connections.\ntype dialer struct {\n\tTimedHandler\n\tstream    Stream\n\tcancel    context.CancelFunc\n\tconn      net.Conn\n\tconnected int32\n\tdone      chan struct{}\n\n\tingressBytesProbe *CounterProbe\n\tegressBytesProbe  *CounterProbe\n}\n\n// NewDialer creates a new handler that dispatches messages in both directions between the given gRPC stream\n// and the given connection.\nfunc NewDialer(\n\tstream Stream,\n\tcancel context.CancelFunc,\n\tingressBytesProbe, egressBytesProbe *CounterProbe,\n) Endpoint {\n\treturn NewConnEndpoint(stream, nil, cancel, ingressBytesProbe, egressBytesProbe)\n}\n\n// NewDialerTTL creates a new handler that dispatches messages in both directions between the given gRPC stream\n// and the given connection. The TTL decides how long the connection can be idle before it's closed.\n//\n// The handler remains active until it's been idle for the ttl duration, at which time it will automatically close\n// and call the release function it got from the tunnel.Pool to ensure that it gets properly released.\nfunc NewDialerTTL(stream Stream, cancel context.CancelFunc, ttl time.Duration, ingressBytesProbe, egressBytesProbe *CounterProbe) Endpoint {\n\treturn NewConnEndpointTTL(stream, nil, cancel, ttl, ingressBytesProbe, egressBytesProbe)\n}\n\nfunc NewConnEndpoint(stream Stream, conn net.Conn, cancel context.CancelFunc, ingressBytesProbe, egressBytesProbe *CounterProbe) Endpoint {\n\tttl := tcpConnTTL\n\tif stream.ID().Protocol() == types.ProtoUDP {\n\t\tttl = udpConnTTL\n\t}\n\treturn NewConnEndpointTTL(stream, conn, cancel, ttl, ingressBytesProbe, egressBytesProbe)\n}\n\nfunc NewConnEndpointTTL(\n\tstream Stream,\n\tconn net.Conn,\n\tcancel context.CancelFunc,\n\tttl time.Duration,\n\tingressBytesProbe, egressBytesProbe *CounterProbe,\n) Endpoint {\n\tstate := notConnected\n\tif conn != nil {\n\t\tstate = connecting\n\t}\n\treturn &dialer{\n\t\tTimedHandler: NewTimedHandler(stream.ID(), ttl, nil),\n\t\tstream:       stream,\n\t\tcancel:       cancel,\n\t\tconn:         conn,\n\t\tconnected:    state,\n\t\tdone:         make(chan struct{}),\n\n\t\tingressBytesProbe: ingressBytesProbe,\n\t\tegressBytesProbe:  egressBytesProbe,\n\t}\n}\n\nfunc (h *dialer) Start(ctx context.Context) {\n\tsr := GetSyntheticIPResolver(ctx)\n\tgo func() {\n\t\tdefer close(h.done)\n\n\t\tid := h.stream.ID()\n\t\ttag := h.stream.Tag()\n\n\t\tswitch h.connected {\n\t\tcase notConnected:\n\t\t\t// Set up the idle timer to close and release this handler when it's been idle for a while.\n\t\t\th.connected = connecting\n\n\t\t\tdto := h.stream.DialTimeout()\n\t\t\tdst := id.Destination()\n\t\t\tdstAddr := dst.Addr()\n\t\t\tif dstAddr.Is6() {\n\t\t\t\taddr, err := sr.Resolve(dstAddr)\n\t\t\t\tif err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"!> %s %s, failed to establish connection: %v\", tag, id, err)\n\t\t\t\t\th.connected = notConnected\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif addr != dstAddr {\n\t\t\t\t\tclog.Debugf(ctx, \"-> %s synthetic destination resolved to %s\", id, addr)\n\t\t\t\t\tid = NewConnID(id.Protocol(), id.Source(), netip.AddrPortFrom(addr, dst.Port()))\n\t\t\t\t\tif dto > localDialTimeout {\n\t\t\t\t\t\tdto = localDialTimeout\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tclog.Debugf(ctx, \"   %s %s, dialing\", tag, id)\n\t\t\td := GetDialer(ctx)\n\t\t\tdtoCtx, cancel := context.WithTimeout(ctx, dto)\n\t\t\tdefer cancel()\n\t\t\tvar conn net.Conn\n\n\t\t\t// A retry is needed here because the attempt to establish a Tunnel might arrive before\n\t\t\t// the target IP is ready to receive requests. The target IP might well be intercepted\n\t\t\t// (or in progress of switching to become intercepted).\n\t\t\terr := backoff.Retry(func() error {\n\t\t\t\tvar err error\n\t\t\t\tif id.Protocol() == types.ProtoUDP {\n\t\t\t\t\tconn, err = d.DialUDP(dtoCtx, netip.AddrPort{}, id.Destination())\n\t\t\t\t} else {\n\t\t\t\t\tconn, err = d.DialTCP(dtoCtx, id.Destination())\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}, backoff.WithContext(backoff.NewConstantBackOff(time.Second), dtoCtx))\n\t\t\tif err != nil {\n\t\t\t\tclog.Errorf(ctx, \"!> %s %s, failed to establish connection: %v\", tag, id, err)\n\t\t\t\tif err = h.stream.Send(ctx, NewMessage(DialReject, nil)); err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"!> %s %s, failed to send DialReject: %v\", tag, id, err)\n\t\t\t\t}\n\t\t\t\tif err = h.stream.CloseSend(ctx); err != nil {\n\t\t\t\t\tclog.Errorf(ctx, \"!> %s %s, stream.CloseSend failed: %v\", tag, id, err)\n\t\t\t\t}\n\t\t\t\th.connected = notConnected\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err = h.stream.Send(ctx, NewMessage(DialOK, nil)); err != nil {\n\t\t\t\t_ = conn.Close()\n\t\t\t\tclog.Errorf(ctx, \"!> %s %s, failed to send DialOK: %v\", tag, id, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tclog.Debugf(ctx, \"<- %s %s, dial answered\", tag, id)\n\t\t\th.conn = conn\n\n\t\tcase connecting:\n\t\tdefault:\n\t\t\tclog.Errorf(ctx, \"!! %s %s, start called in invalid state\", tag, id)\n\t\t\treturn\n\t\t}\n\n\t\t// Set up the idle timer to close and release this endpoint when it's been idle for a while.\n\t\th.TimedHandler.Start(ctx)\n\t\th.connected = connected\n\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(2)\n\t\tgo h.connToStreamLoop(ctx, &wg)\n\t\tgo h.streamToConnLoop(ctx, &wg)\n\t\twg.Wait()\n\t\th.Stop(ctx)\n\t}()\n}\n\nfunc (h *dialer) Done() <-chan struct{} {\n\treturn h.done\n}\n\n// Stop will close the underlying TCP/UDP connection.\nfunc (h *dialer) Stop(ctx context.Context) {\n\th.startDisconnect(ctx, \"explicit close\", true)\n\th.startDisconnect(ctx, \"explicit close\", false)\n\th.cancel()\n}\n\nfunc (h *dialer) startDisconnect(ctx context.Context, reason string, isReader bool) {\n\tif wrConn, ok := h.conn.(halfCloser); ok {\n\t\tif isReader {\n\t\t\th.startReaderDisconnect(ctx, reason, wrConn)\n\t\t} else {\n\t\t\th.startWriterDisconnect(ctx, reason, wrConn)\n\t\t}\n\t} else if atomic.CompareAndSwapInt32(&h.connected, connected, notConnected) {\n\t\tclog.Tracef(ctx, \"<> %s %s closing connection: %s\", h.stream.Tag(), h.stream.ID(), reason)\n\t\tif err := h.conn.Close(); err != nil {\n\t\t\tclog.Tracef(ctx, \"!! %s %s, Close failed: %v\", h.stream.Tag(), h.stream.ID(), err)\n\t\t}\n\t}\n}\n\nfunc (h *dialer) startReaderDisconnect(ctx context.Context, reason string, conn halfCloser) {\n\tif atomic.CompareAndSwapInt32(&h.connected, connected, readClosed) || atomic.CompareAndSwapInt32(&h.connected, writeClosed, notConnected) {\n\t\tclog.Tracef(ctx, \"<- %s %s closing connection write: %s\", h.stream.Tag(), h.stream.ID(), reason)\n\t\tif err := conn.CloseWrite(); err != nil && !strings.HasSuffix(err.Error(), \"not connected\") {\n\t\t\tclog.Debugf(ctx, \"<! %s %s, CloseWrite failed: %v\", h.stream.Tag(), h.stream.ID(), err)\n\t\t}\n\t\treturn\n\t}\n}\n\nfunc (h *dialer) startWriterDisconnect(ctx context.Context, reason string, conn halfCloser) {\n\tif atomic.CompareAndSwapInt32(&h.connected, connected, writeClosed) || atomic.CompareAndSwapInt32(&h.connected, readClosed, notConnected) {\n\t\tclog.Tracef(ctx, \"-> %s %s closing connection read: %s\", h.stream.Tag(), h.stream.ID(), reason)\n\t\terr := conn.CloseRead()\n\t\tswitch {\n\t\tcase err == nil, err == io.EOF, strings.Contains(err.Error(), \"not connected\"):\n\t\tdefault:\n\t\t\tclog.Debugf(ctx, \"!< %s %s, CloseRead failed: %v\", h.stream.Tag(), h.stream.ID(), err)\n\t\t}\n\t}\n}\n\nfunc (h *dialer) connToStreamLoop(ctx context.Context, wg *sync.WaitGroup) {\n\tvar endReason string\n\tendLevel := clog.LevelTrace\n\tid := h.stream.ID()\n\ttag := h.stream.Tag()\n\n\t// Outgoing must not be buffered. It's essential that messages that are read from the connection\n\t// are sent on the stream a.s.a.p. and that a delay when doing that causes back-pressure on the\n\t// connection.\n\toutgoing := make(chan Message)\n\tdefer func() {\n\t\tif !h.ResetIdle() {\n\t\t\t// Hard close of peer. We don't want any more data\n\t\t\tselect {\n\t\t\tcase outgoing <- NewMessage(Disconnect, nil):\n\t\t\tdefault:\n\t\t\t}\n\t\t}\n\t\tclose(outgoing)\n\t\tclog.Logf(ctx, endLevel, \"<- %s %s conn-to-stream loop ended because %s\", tag, id, endReason)\n\t\twg.Done()\n\t}()\n\n\twg.Add(1)\n\tWriteLoop(ctx, h.stream, outgoing, wg, h.egressBytesProbe)\n\n\tbuf := make([]byte, 0x80000)\n\tclog.Tracef(ctx, \"-> %s %s conn-to-stream loop started\", tag, id)\n\tfor {\n\t\t_ = h.conn.SetReadDeadline(time.Now().Add(200 * time.Millisecond))\n\t\tn, err := h.conn.Read(buf)\n\t\tif n > 0 {\n\t\t\tclog.Tracef(ctx, \"-> %s %s, read len %d from conn\", tag, id, n)\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tendReason = ctx.Err().Error()\n\t\t\t\treturn\n\t\t\tcase outgoing <- NewMessage(Normal, buf[:n]):\n\t\t\t}\n\t\t}\n\n\t\tif err != nil {\n\t\t\tvar netErr *net.OpError\n\t\t\tswitch {\n\t\t\tcase errors.Is(err, os.ErrDeadlineExceeded), errors.As(err, &netErr) && netErr.Timeout():\n\t\t\t\tif ctx.Err() != nil {\n\t\t\t\t\tendReason = ctx.Err().Error()\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\tcase errors.Is(err, io.EOF):\n\t\t\t\tendReason = \"EOF was encountered\"\n\t\t\tcase errors.Is(err, net.ErrClosed):\n\t\t\t\tendReason = \"the connection was closed\"\n\t\t\tcase strings.Contains(err.Error(), \"aborted\"):\n\t\t\t\tendReason = \"the connection was aborted\"\n\t\t\tdefault:\n\t\t\t\tendReason = fmt.Sprintf(\"a read error occurred: %T %v\", err, err)\n\t\t\t\tendLevel = slog.LevelError\n\t\t\t}\n\t\t\th.startDisconnect(ctx, endReason, false)\n\t\t\treturn\n\t\t}\n\n\t\tif !h.ResetIdle() {\n\t\t\tendReason = \"it was idle for too long\"\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (h *dialer) getStream() Stream {\n\treturn h.stream\n}\n\nfunc (h *dialer) reply(data []byte) (int, error) {\n\treturn h.conn.Write(data)\n}\n\nfunc (h *dialer) streamToConnLoop(ctx context.Context, wg *sync.WaitGroup) {\n\tdefer func() {\n\t\twg.Done()\n\t}()\n\treadLoop(ctx, h.stream.Tag(), h, h.ingressBytesProbe)\n}\n\nfunc handleControl(ctx context.Context, h streamReader, cm Message) {\n\tswitch cm.Code() {\n\tcase DialReject, Disconnect: // Peer wants to hard-close. No more messages will arrive\n\t\th.Stop(ctx)\n\tcase KeepAlive:\n\t\th.ResetIdle()\n\tcase DialOK:\n\t\t// So how can a dialer get a DialOK from a peer? Surely, there cannot be a dialer at both ends?\n\t\t// Well, the story goes like this:\n\t\t// 1. A request to the service is made on the workstation.\n\t\t// 2. This agent's listener receives a connection.\n\t\t// 3. Since an intercept is active, the agent creates a tunnel to the workstation\n\t\t// 4. A new dialer is attached to that tunnel (reused as a tunnel endpoint)\n\t\t// 5. The dialer at the workstation dials and responds with DialOK, and here we are.\n\tdefault:\n\t\tclog.Errorf(ctx, \"!! CONN %s: unhandled connection control message: %s\", h.getStream().ID(), cm)\n\t}\n}\n\nfunc readLoop(ctx context.Context, tag Tag, h streamReader, trafficProbe *CounterProbe) {\n\tvar endReason string\n\tendLevel := clog.LevelTrace\n\tid := h.getStream().ID()\n\tdefer func() {\n\t\th.startDisconnect(ctx, endReason, true)\n\t\tclog.Logf(ctx, endLevel, \"<- %s %s stream-to-conn loop ended because %s\", tag, id, endReason)\n\t}()\n\n\tincoming, errCh := ReadLoop(ctx, h.getStream(), trafficProbe)\n\tclog.Tracef(ctx, \"<- %s %s stream-to-conn loop started\", tag, id)\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tendReason = ctx.Err().Error()\n\t\t\treturn\n\t\tcase <-h.Idle():\n\t\t\tendReason = \"it was idle for too long\"\n\t\t\treturn\n\t\tcase err, ok := <-errCh:\n\t\t\tif ok {\n\t\t\t\tclog.Error(ctx, err)\n\t\t\t}\n\t\tcase dg, ok := <-incoming:\n\t\t\tif !ok {\n\t\t\t\t// h.incoming was closed by the reader and is now drained.\n\t\t\t\tendReason = \"there was no more input\"\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !h.ResetIdle() {\n\t\t\t\tendReason = \"it was idle for too long\"\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif dg.Code() != Normal {\n\t\t\t\thandleControl(ctx, h, dg)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpayload := dg.Payload()\n\t\t\tpn := len(payload)\n\t\t\tfor n := 0; n < pn; {\n\t\t\t\twn, err := h.reply(payload[n:])\n\t\t\t\tif err != nil {\n\t\t\t\t\tendReason = fmt.Sprintf(\"a write error occurred: %v\", err)\n\t\t\t\t\tendLevel = slog.LevelError\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tclog.Tracef(ctx, \"<- %s %s, len %d\", tag, id, wn)\n\t\t\t\tn += wn\n\t\t\t}\n\t\t}\n\t}\n}\n\n// DialWaitLoop reads from the given dialStream. A new goroutine that creates a Tunnel to the manager and then\n// attaches a dialer Endpoint to that tunnel is spawned for each request that arrives. The method blocks until\n// the dialStream is closed.\nfunc DialWaitLoop(\n\tctx context.Context,\n\ttag Tag,\n\ttunnelProvider Provider,\n\tdialStream agent.Agent_WatchDialClient,\n\tsessionID SessionID,\n) error {\n\t// create ctx to clean up leftover dialRespond if waitloop dies\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tfor ctx.Err() == nil {\n\t\tdr, err := dialStream.Recv()\n\t\tif err == nil {\n\t\t\tgo dialRespond(ctx, tag, tunnelProvider, dr, sessionID)\n\t\t\tcontinue\n\t\t}\n\t\tif ctx.Err() != nil {\n\t\t\treturn nil\n\t\t}\n\t\tif errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {\n\t\t\treturn nil\n\t\t}\n\t\tswitch status.Code(err) {\n\t\tcase codes.Canceled, codes.NotFound, codes.Unavailable:\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"dial request stream recv: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc dialRespond(ctx context.Context, tag Tag, tunnelProvider Provider, dr *rpc.DialRequest, sessionID SessionID) {\n\tid := ConnID(dr.ConnId)\n\tctx, cancel := context.WithCancel(ctx)\n\tmt, err := tunnelProvider.Tunnel(ctx)\n\tif err != nil {\n\t\tclog.Errorf(ctx, \"!! %s %s, call to manager Tunnel failed: %v\", tag, id, err)\n\t\tcancel()\n\t\treturn\n\t}\n\ts, err := NewClientStream(ctx, tag, mt, id, sessionID, time.Duration(dr.RoundtripLatency), time.Duration(dr.DialTimeout))\n\tif err != nil {\n\t\tclog.Error(ctx, err)\n\t\tcancel()\n\t\treturn\n\t}\n\td := NewDialer(s, cancel, nil, nil)\n\td.Start(ctx)\n\t<-d.Done()\n}\n"
  },
  {
    "path": "pkg/tunnel/message.go",
    "content": "package tunnel\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n)\n\ntype MessageCode byte\n\nconst (\n\tNormal = MessageCode(iota)\n\tstreamInfo\n\tstreamOK\n\n\t// closeSend is sent when:\n\t//\n\t// - a TCP client receives a FIN, after it changes its state to CLOSE_WAIT\n\t//\n\t// - a TCP client receives a FIN, after it changes its state to FIN_WAIT_2\n\t//\n\t// - a Dialer's connection has been closed by the peer\n\t//\n\t// - a Listener Endpoint's connection is closed by the peer\n\t//\n\t// When a TCP client receives a closeSend on its stream, the following applies depending on state:\n\t//\n\t// - in CLOSE_WAIT, the client sends a FIN, and enters the LAST_ACK state\n\t//\n\t// - in SYN_RECEIVED or ESTABLISHED, the client sends a FIN, and enters the FIN_WAIT_1 state\n\t//\n\t// If a Dialer or Endpoint receives a closeSend, it must close its connection and its ReadLoop\n\t// (no more data will arrive on the stream) but its WriteLoop must continue to operate until the\n\t// connection reports EOF or closed, at which time a closeSend is sent in the opposite direction.\n\tcloseSend\n\n\t// DialOK is sent when a Dialer successfully dialed its connection.\n\t//\n\t// A TCP client that receives a DialOK must transit from state SYN_RECEIVED to ESTABLISHED.\n\tDialOK\n\n\t// DialReject is sent when a Dialer fails to dial its connection.\n\t//\n\t// A TCP client that receives a DialReject must send an RST and transit from state SYN_RECEIVED to CLOSED.\n\tDialReject\n\n\t// Disconnect is sent when\n\t//\n\t// - A TCP client receives a RST, after it has changed its state to CLOSED\n\t//\n\t// - A Dialer or Listener Endpoint has been made unavailable for other reasons than a proper close or EOF.\n\tDisconnect\n\n\tKeepAlive\n\tSession\n)\n\nfunc (c MessageCode) String() string {\n\tswitch c {\n\tcase streamInfo:\n\t\treturn \"STREAM_INFO\"\n\tcase streamOK:\n\t\treturn \"STREAM_OK\"\n\tcase closeSend:\n\t\treturn \"CLOSE_SEND\"\n\tcase DialOK:\n\t\treturn \"DIAL_OK\"\n\tcase DialReject:\n\t\treturn \"DIAL_REJECT\"\n\tcase Disconnect:\n\t\treturn \"DISCONNECT_OK\"\n\tcase KeepAlive:\n\t\treturn \"KEEP_ALIVE\"\n\tcase Session:\n\t\treturn \"SESSION\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"** unknown control code: %d **\", c)\n\t}\n}\n\ntype Message interface {\n\tCode() MessageCode\n\tPayload() []byte\n\tTunnelMessage() *manager.TunnelMessage\n}\n\ntype msg []byte\n\nfunc (c msg) Code() MessageCode {\n\treturn MessageCode(c[0])\n}\n\nfunc (c msg) Payload() []byte {\n\treturn c[1:]\n}\n\nfunc (c msg) String() string {\n\tcode := c.Code()\n\tif code == Normal {\n\t\treturn fmt.Sprintf(\"len %d\", len(c.Payload()))\n\t}\n\treturn fmt.Sprintf(\"code %s, len %d\", code, len(c.Payload()))\n}\n\nfunc (c msg) TunnelMessage() *manager.TunnelMessage {\n\treturn &manager.TunnelMessage{Payload: c}\n}\n\nfunc NewMessage(code MessageCode, payload []byte) Message {\n\tif pl := len(payload); pl > 0 {\n\t\tc := makeMessage(code, len(payload))\n\t\tcopy(c.Payload(), payload)\n\t\treturn c\n\t}\n\treturn msg{byte(code)}\n}\n\nfunc StreamInfoMessage(id ConnID, sessionID SessionID, callDelay, dialTimeout time.Duration) Message {\n\tb := bytes.Buffer{}\n\tb.WriteByte(byte(streamInfo))\n\n\tbuf := make([]byte, 8)\n\tn := binary.PutUvarint(buf, uint64(Version))\n\tb.Write(buf[:n])\n\n\tn = binary.PutUvarint(buf, uint64(callDelay))\n\tb.Write(buf[:n])\n\n\tn = binary.PutUvarint(buf, uint64(dialTimeout))\n\tb.Write(buf[:n])\n\n\tidb := []byte(id)\n\tn = binary.PutUvarint(buf, uint64(len(idb)))\n\tb.Write(buf[:n])\n\tb.Write(idb)\n\n\tsb := []byte(sessionID)\n\tn = binary.PutUvarint(buf, uint64(len(sb)))\n\tb.Write(buf[:n])\n\tb.Write(sb)\n\treturn msg(b.Bytes())\n}\n\nfunc StreamOKMessage() Message {\n\tm := makeMessage(streamOK, 4)\n\tn := binary.PutUvarint(m.Payload(), uint64(Version))\n\treturn m[:n+1]\n}\n\nfunc SessionMessage(sessionID SessionID) Message {\n\treturn NewMessage(Session, []byte(sessionID))\n}\n\nfunc GetSession(m Message) SessionID {\n\treturn SessionID(m.Payload())\n}\n\nfunc makeMessage(code MessageCode, payloadLength int) msg {\n\tm := make(msg, 1+payloadLength)\n\tm[0] = byte(code)\n\treturn m\n}\n\n// getVersion returns the version that this Message represents.\nfunc getVersion(m Message) uint16 {\n\tv, _ := binary.Uvarint(m.Payload())\n\treturn uint16(v)\n}\n\nvar errMalformedConnect = errors.New(\"malformed Connect message\")\n\n// connectInfo returns the connectInfo that this Message represents.\nfunc setConnectInfo(m Message, s *stream) error {\n\tpl := m.Payload()\n\n\tv, n := binary.Uvarint(pl)\n\tif n <= 0 {\n\t\treturn errMalformedConnect\n\t}\n\ts.peerVersion = uint16(v)\n\tpl = pl[n:]\n\n\tv, n = binary.Uvarint(pl)\n\tif n <= 0 {\n\t\treturn errMalformedConnect\n\t}\n\ts.roundtripLatency = time.Duration(v)\n\tpl = pl[n:]\n\n\tv, n = binary.Uvarint(pl)\n\tif n <= 0 {\n\t\treturn errMalformedConnect\n\t}\n\ts.dialTimeout = time.Duration(v)\n\tpl = pl[n:]\n\n\tv, n = binary.Uvarint(pl)\n\tif n <= 0 || v > uint64(len(pl)) {\n\t\treturn errMalformedConnect\n\t}\n\tpl = pl[n:]\n\ts.id = ConnID(pl[:v])\n\tpl = pl[v:]\n\n\tv, n = binary.Uvarint(pl)\n\tif n <= 0 || v > uint64(len(pl)) {\n\t\treturn errMalformedConnect\n\t}\n\tpl = pl[n:]\n\ts.sessionID = SessionID(pl[:v])\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/tunnel/metrics.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n)\n\ntype StreamProvider interface {\n\tCreateClientStream(ctx context.Context, tag Tag, clientSessionID SessionID, id ConnID, roundTripLatency, dialTimeout time.Duration) (Stream, error)\n}\n\ntype ClientStreamProvider interface {\n\tCreateClientStream(ctx context.Context, tag Tag, clientSessionID SessionID, id ConnID, roundTripLatency, dialTimeout time.Duration) (Stream, error)\n\tReportMetrics(ctx context.Context, metrics *manager.TunnelMetrics)\n\tMetricsEnabled() bool\n}\n\ntype TrafficManagerStreamProvider struct {\n\tManager        manager.ManagerClient\n\tAgentSessionID SessionID\n}\n\nfunc (sp *TrafficManagerStreamProvider) CreateClientStream(\n\tctx context.Context,\n\ttag Tag,\n\tclientSessionID SessionID,\n\tid ConnID,\n\troundTripLatency,\n\tdialTimeout time.Duration,\n) (Stream, error) {\n\tclog.Debugf(ctx, \"creating tunnel to manager for id %s\", id)\n\tms, err := sp.Manager.Tunnel(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"call to manager.Tunnel() failed. Id %s: %v\", id, err)\n\t}\n\n\ts, err := NewClientStream(ctx, tag, ms, id, sp.AgentSessionID, roundTripLatency, dialTimeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err = s.Send(ctx, SessionMessage(clientSessionID)); err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to send client session id. Id %s: %v\", id, err)\n\t}\n\treturn s, nil\n}\n"
  },
  {
    "path": "pkg/tunnel/pipe.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"time\"\n)\n\n// NewPipe creates a pair of Streams connected using two channels.\nfunc NewPipe(id ConnID, sessionID SessionID, tagA, tagB Tag) (Stream, Stream) {\n\tout := make(chan Message, 1)\n\tin := make(chan Message, 1)\n\treturn &channelStream{\n\t\t\tid:     id,\n\t\t\tsid:    sessionID,\n\t\t\ttag:    tagA,\n\t\t\trecvCh: in,\n\t\t\tsendCh: out,\n\t\t}, &channelStream{\n\t\t\tid:     id,\n\t\t\tsid:    sessionID,\n\t\t\ttag:    tagB,\n\t\t\trecvCh: out,\n\t\t\tsendCh: in,\n\t\t}\n}\n\ntype channelStream struct {\n\tid     ConnID\n\tsid    SessionID\n\ttag    Tag\n\trecvCh <-chan Message\n\tsendCh chan<- Message\n}\n\nfunc (s channelStream) SetTag(_ Tag) {\n}\n\nfunc (s channelStream) Tag() Tag {\n\treturn s.tag\n}\n\nfunc (s channelStream) ID() ConnID {\n\treturn s.id\n}\n\nfunc (s channelStream) Receive(ctx context.Context) (Message, error) {\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn nil, ctx.Err()\n\tcase m, ok := <-s.recvCh:\n\t\tif !ok {\n\t\t\treturn nil, io.EOF\n\t\t}\n\t\treturn m, nil\n\t}\n}\n\nfunc (s channelStream) Send(ctx context.Context, message Message) error {\n\tselect {\n\tcase <-ctx.Done():\n\tcase s.sendCh <- message:\n\t}\n\treturn nil\n}\n\nfunc (s channelStream) CloseSend(_ context.Context) error {\n\tclose(s.sendCh)\n\treturn nil\n}\n\nfunc (s channelStream) PeerVersion() uint16 {\n\treturn 2\n}\n\nfunc (s channelStream) SessionID() SessionID {\n\treturn s.sid\n}\n\nfunc (s channelStream) DialTimeout() time.Duration {\n\treturn time.Second\n}\n\nfunc (s channelStream) RoundtripLatency() time.Duration {\n\treturn time.Millisecond\n}\n"
  },
  {
    "path": "pkg/tunnel/pool.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\ntype Pool struct {\n\thandlers map[ConnID]Handler\n\tlock     sync.RWMutex\n}\n\ntype Handler interface {\n\t// Stop closes the handle\n\tStop(context.Context)\n\n\tStart(ctx context.Context)\n}\n\nfunc NewPool() *Pool {\n\treturn &Pool{handlers: make(map[ConnID]Handler)}\n}\n\nfunc (p *Pool) release(ctx context.Context, id ConnID) {\n\tp.lock.Lock()\n\tdelete(p.handlers, id)\n\tcount := len(p.handlers)\n\tp.lock.Unlock()\n\tclog.Debugf(ctx, \"-- POOL %s, count now is %d\", id, count)\n}\n\n// HandlerCreator describes the function signature for the function that creates a handler.\ntype HandlerCreator func(ctx context.Context, release func()) (Handler, error)\n\n// Get finds a handler for the given id from the pool and returns it. Nil is returned if no such handler exists.\nfunc (p *Pool) Get(id ConnID) Handler {\n\tp.lock.RLock()\n\thandler := p.handlers[id]\n\tp.lock.RUnlock()\n\treturn handler\n}\n\n// GetOrCreate finds a handler for the given id from the pool, or creates a new handler using the given createHandler func\n// when no handler was found. The handler is returned together with a boolean flag which is set to true if\n// the handler was found or false if it was created.\nfunc (p *Pool) GetOrCreate(ctx context.Context, id ConnID, createHandler HandlerCreator) (Handler, bool, error) {\n\tp.lock.RLock()\n\thandler, ok := p.handlers[id]\n\tp.lock.RUnlock()\n\n\tif ok {\n\t\treturn handler, true, nil\n\t}\n\n\thandlerCtx, cancel := context.WithCancel(ctx)\n\n\trelease := func() {\n\t\tp.release(ctx, id)\n\t\tcancel()\n\t}\n\n\tvar err error\n\thandler, err = createHandler(handlerCtx, release)\n\tif err != nil {\n\t\treturn nil, false, err\n\t}\n\tif handler == nil {\n\t\treturn nil, false, errors.New(\"createHandler function did not produce a handler\")\n\t}\n\n\tp.lock.Lock()\n\tvar old Handler\n\tif old, ok = p.handlers[id]; !ok {\n\t\tp.handlers[id] = handler\n\t}\n\tcount := len(p.handlers)\n\tp.lock.Unlock()\n\tif ok {\n\t\t// Toss newly created handler. It's not started anyway.\n\t\treturn old, true, nil\n\t}\n\thandler.Start(handlerCtx)\n\tclog.Debugf(ctx, \"++ POOL %s, count now is %d\", id, count)\n\treturn handler, false, nil\n}\n\nfunc (p *Pool) CloseAll(ctx context.Context) {\n\tp.lock.RLock()\n\thandlers := make([]Handler, len(p.handlers))\n\ti := 0\n\tfor _, handler := range p.handlers {\n\t\thandlers[i] = handler\n\t\ti++\n\t}\n\tp.lock.RUnlock()\n\n\tfor _, handler := range handlers {\n\t\thandler.Stop(ctx)\n\t}\n}\n"
  },
  {
    "path": "pkg/tunnel/probe.go",
    "content": "package tunnel\n\nimport (\n\t\"sync/atomic\"\n)\n\ntype CounterProbe struct {\n\tname  string\n\tvalue uint64\n}\n\nfunc NewCounterProbe(name string) *CounterProbe {\n\treturn &CounterProbe{name: name}\n}\n\nfunc (p *CounterProbe) Increment(v uint64) {\n\tatomic.AddUint64(&p.value, v)\n}\n\nfunc (p *CounterProbe) GetName() string {\n\treturn p.name\n}\n\nfunc (p *CounterProbe) GetValue() uint64 {\n\treturn atomic.LoadUint64(&p.value)\n}\n"
  },
  {
    "path": "pkg/tunnel/provider.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\n\t\"google.golang.org/grpc\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/agent\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n)\n\ntype Client interface {\n\tSend(*rpc.TunnelMessage) error\n\tRecv() (*rpc.TunnelMessage, error)\n\tgrpc.ClientStream\n}\n\ntype Provider interface {\n\tTunnel(ctx context.Context, opts ...grpc.CallOption) (Client, error)\n}\n\ntype mgrProvider struct {\n\trpc.ManagerClient\n}\n\nfunc (m mgrProvider) Tunnel(ctx context.Context, opts ...grpc.CallOption) (Client, error) {\n\treturn m.ManagerClient.Tunnel(ctx, opts...)\n}\n\nfunc ManagerProvider(m rpc.ManagerClient) Provider {\n\treturn mgrProvider{m}\n}\n\ntype agentProvider struct {\n\tagent.AgentClient\n}\n\nfunc (m agentProvider) Tunnel(ctx context.Context, opts ...grpc.CallOption) (Client, error) {\n\treturn m.AgentClient.Tunnel(ctx, opts...)\n}\n\nfunc AgentProvider(a agent.AgentClient) Provider {\n\treturn agentProvider{a}\n}\n"
  },
  {
    "path": "pkg/tunnel/server_stream.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n)\n\nfunc NewServerStream(ctx context.Context, tag Tag, grpcStream GRPCStream) (Stream, error) {\n\ts := &stream{tag: tag, grpcStream: grpcStream, syncRatio: 8, ackWindow: 1}\n\tm, err := s.Receive(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read initial StreamInfo message: %w\", err)\n\t}\n\tif m.Code() != streamInfo {\n\t\treturn nil, errors.New(\"initial message was not StreamInfo\")\n\t}\n\tif err = setConnectInfo(m, s); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse StreamInfo message: %w\", err)\n\t}\n\tif err = s.Send(ctx, StreamOKMessage()); err != nil {\n\t\treturn nil, err\n\t}\n\treturn s, nil\n}\n"
  },
  {
    "path": "pkg/tunnel/stream.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\n\t\"github.com/telepresenceio/clog\"\n\trpc \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n)\n\n// Version of the stream protocol.\n//\n//\t0 which didn't report versions and didn't do synchronization\n//\t1 used MuxTunnel instead of one tunnel per connection.\nconst Version = uint16(2)\n\ntype (\n\tSessionID string\n\tTag       string\n)\n\nconst (\n\tTunToClient     = Tag(\"TUN⇄CLI\")\n\tTunToDNS        = Tag(\"TUN⇄DNS\")\n\tDnsToTun        = Tag(\"DNS⇄TUN\")\n\tClientToAgent   = Tag(\"CLI⇄AGN\")\n\tClientToDNS     = Tag(\"CLI⇄DNS\")\n\tAgentToClient   = Tag(\"AGN⇄CLI\")\n\tAgentToProxied  = Tag(\"AGN⇄PRX\")\n\tClientToManager = Tag(\"CLI⇄MGR\")\n\tManagerToClient = Tag(\"MGR⇄CLI\")\n)\n\n// Endpoint is an endpoint for a Stream such as a Dialer or a bidirectional pipe.\ntype Endpoint interface {\n\tStart(ctx context.Context)\n\tDone() <-chan struct{}\n}\n\n// GRPCStream is the bare minimum needed for reading and writing TunnelMessages\n// on a Manager_TunnelServer or Manager_TunnelClient.\ntype GRPCStream interface {\n\tRecv() (*rpc.TunnelMessage, error)\n\tSend(*rpc.TunnelMessage) error\n}\n\n// GRPCContextStream is similar to GRPCStream but also allows the caller to pass a context.Context\n// to Recv and Send so that the stream can be canceled.\ntype GRPCContextStream interface {\n\tRecvContext(context.Context) (*rpc.TunnelMessage, error)\n\tSendContext(context.Context, *rpc.TunnelMessage) error\n}\n\n// The Stream interface represents a bidirectional, synchronized connection Tunnel\n// that sends TCP or UDP traffic over gRPC using manager.TunnelMessage messages.\n//\n// A Stream is closed by one of six things happening at either end (or at both ends).\n//\n//  1. Read from local connection fails (typically EOF)\n//  2. Write to local connection fails (connection peer closed)\n//  3. Idle timer timed out.\n//  4. Context is cancelled.\n//  5. closeSend request received from Tunnel peer.\n//  6. Disconnect received from Tunnel peer.\n//\n// When #1 or #2 happens, the Stream will either call CloseSend() (if it's a client Stream)\n// or send a closeSend request (if it's a StreamServer) to its Stream peer, shorten the\n// Idle timer, and then continue to serve incoming data from the Stream peer until it's\n// closed or a Disconnect is received. Once that happens, it's guaranteed that the Tunnel\n// peer will send no more messages and the Stream is closed.\n//\n// When #3, #4, or #5 happens, the Tunnel will send a Disconnect to its Stream peer and close.\n//\n// When #6 happens, the Stream will simply close.\ntype Stream interface {\n\tTag() Tag\n\tID() ConnID\n\tReceive(context.Context) (Message, error)\n\tSend(context.Context, Message) error\n\tCloseSend(ctx context.Context) error\n\tPeerVersion() uint16\n\tSessionID() SessionID\n\tDialTimeout() time.Duration\n\tRoundtripLatency() time.Duration\n\tSetTag(tag Tag)\n}\n\n// StreamCreator is a function that creats a Stream.\ntype StreamCreator func(context.Context, ConnID) (Stream, error)\n\n// ReadLoop reads from the Stream and dispatches messages and error to the give channels. There\n// will be max one error since the error also terminates the loop.\nfunc ReadLoop(ctx context.Context, s Stream, p *CounterProbe) (<-chan Message, <-chan error) {\n\tmsgCh := make(chan Message, 50)\n\terrCh := make(chan error, 1) // Max one message will be sent on this channel\n\tclog.Tracef(ctx, \"<- %s %s, ReadLoop starting\", s.Tag(), s.ID())\n\tgo func() {\n\t\tvar endReason string\n\t\tdefer func() {\n\t\t\tclose(errCh)\n\t\t\tclose(msgCh)\n\t\t\tclog.Tracef(ctx, \"<- %s %s, ReadLoop ended: %s\", s.Tag(), s.ID(), endReason)\n\t\t}()\n\n\t\tfor {\n\t\t\tm, err := s.Receive(ctx)\n\t\t\tif m != nil && p != nil {\n\t\t\t\tp.Increment(uint64(len(m.Payload())))\n\t\t\t}\n\n\t\t\tswitch {\n\t\t\tcase err == nil:\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\tendReason = ctx.Err().Error()\n\t\t\t\tcase msgCh <- m:\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\tcase ctx.Err() != nil:\n\t\t\t\tendReason = ctx.Err().Error()\n\t\t\tcase errors.Is(err, io.EOF):\n\t\t\t\tendReason = \"EOF on input\"\n\t\t\tcase errors.Is(err, net.ErrClosed):\n\t\t\t\tendReason = \"stream closed\"\n\t\t\tcase errors.Is(err, context.Canceled):\n\t\t\t\tendReason = err.Error()\n\t\t\tdefault:\n\t\t\t\tswitch status.Code(err) {\n\t\t\t\tcase codes.NotFound:\n\t\t\t\t\tendReason = \"session closed\"\n\t\t\t\tcase codes.Canceled:\n\t\t\t\t\tendReason = err.Error()\n\t\t\t\tcase codes.Unavailable:\n\t\t\t\t\tif strings.HasSuffix(err.Error(), \"reading from server: EOF\") {\n\t\t\t\t\t\tendReason = err.Error()\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t\tfallthrough\n\t\t\t\tdefault:\n\t\t\t\t\tendReason = err.Error()\n\t\t\t\t\tselect {\n\t\t\t\t\tcase errCh <- fmt.Errorf(\"<! %s %s, read from grpc.ClientStream failed: %w\", s.Tag(), s.ID(), err):\n\t\t\t\t\tdefault:\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}()\n\treturn msgCh, errCh\n}\n\n// WriteLoop reads messages from the channel and writes them to the Stream. It will call CloseSend() on the\n// stream when the channel is closed.\nfunc WriteLoop(\n\tctx context.Context,\n\ts Stream, msgCh <-chan Message,\n\twg *sync.WaitGroup,\n\tp *CounterProbe,\n) {\n\tclog.Tracef(ctx, \"-> %s %s, WriteLoop starting\", s.Tag(), s.ID())\n\tgo func() {\n\t\tvar endReason string\n\t\tdefer func() {\n\t\t\tclog.Tracef(ctx, \"   %s %s, WriteLoop ended: %s\", s.Tag(), s.ID(), endReason)\n\t\t\tif err := s.CloseSend(ctx); err != nil {\n\t\t\t\tclog.Errorf(ctx, \"!> %s %s, Send of closeSend failed: %v\", s.Tag(), s.ID(), err)\n\t\t\t}\n\t\t\twg.Done()\n\t\t}()\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\tendReason = ctx.Err().Error()\n\t\t\tcase m, ok := <-msgCh:\n\t\t\t\tif !ok {\n\t\t\t\t\tendReason = \"input channel is closed\"\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\terr := s.Send(ctx, m)\n\t\t\t\tif m != nil && p != nil {\n\t\t\t\t\tp.Increment(uint64(len(m.Payload())))\n\t\t\t\t}\n\n\t\t\t\tswitch {\n\t\t\t\tcase err == nil:\n\t\t\t\t\tcontinue\n\t\t\t\tcase errors.Is(err, net.ErrClosed):\n\t\t\t\t\tendReason = \"output stream is closed\"\n\t\t\t\tdefault:\n\t\t\t\t\tendReason = err.Error()\n\t\t\t\t\tclog.Errorf(ctx, \"!! %s %s, Send failed: %v\", s.Tag(), s.ID(), err)\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}()\n}\n\ntype stream struct {\n\tgrpcStream       GRPCStream\n\tid               ConnID\n\tdialTimeout      time.Duration\n\troundtripLatency time.Duration\n\tsessionID        SessionID\n\ttag              Tag\n\tsyncRatio        uint32 // send and check sync after each syncRatio message\n\tackWindow        uint32 // maximum permitted difference between sent and received ack\n\tpeerVersion      uint16\n}\n\nfunc newStream(tag Tag, grpcStream GRPCStream) stream {\n\treturn stream{tag: tag, grpcStream: grpcStream, syncRatio: 8, ackWindow: 1}\n}\n\nfunc (s *stream) Tag() Tag {\n\treturn s.tag\n}\n\nfunc (s *stream) SetTag(tag Tag) {\n\ts.tag = tag\n}\n\nfunc (s *stream) ID() ConnID {\n\treturn s.id\n}\n\nfunc (s *stream) PeerVersion() uint16 {\n\treturn s.peerVersion\n}\n\nfunc (s *stream) DialTimeout() time.Duration {\n\treturn s.dialTimeout\n}\n\nfunc (s *stream) RoundtripLatency() time.Duration {\n\treturn s.roundtripLatency\n}\n\nfunc (s *stream) SessionID() SessionID {\n\treturn s.sessionID\n}\n\nfunc (s *stream) Receive(ctx context.Context) (Message, error) {\n\tvar cm *rpc.TunnelMessage\n\tvar err error\n\tif streamCtx, ok := s.grpcStream.(GRPCContextStream); ok {\n\t\tcm, err = streamCtx.RecvContext(ctx)\n\t} else {\n\t\tcm, err = s.grpcStream.Recv()\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tm := msg(cm.Payload)\n\tswitch m.Code() {\n\tcase closeSend:\n\t\tclog.Tracef(ctx, \"<- %s %s, close send\", s.tag, s.id)\n\t\treturn nil, net.ErrClosed\n\tcase streamInfo:\n\t\tclog.Tracef(ctx, \"<- %s, %s\", s.tag, m)\n\tdefault:\n\t\tclog.Tracef(ctx, \"<- %s %s, %s\", s.tag, s.id, m)\n\t}\n\treturn m, nil\n}\n\nfunc (s *stream) Send(ctx context.Context, m Message) error {\n\tvar err error\n\tif streamCtx, ok := s.grpcStream.(GRPCContextStream); ok {\n\t\terr = streamCtx.SendContext(ctx, m.TunnelMessage())\n\t} else {\n\t\terr = s.grpcStream.Send(m.TunnelMessage())\n\t}\n\tif err != nil {\n\t\tif ctx.Err() == nil && !errors.Is(err, net.ErrClosed) {\n\t\t\tclog.Errorf(ctx, \"!! %s %s, Send failed: %v\", s.tag, s.id, err)\n\t\t}\n\t\treturn err\n\t}\n\tclog.Tracef(ctx, \"-> %s %s, %s\", s.tag, s.id, m)\n\treturn nil\n}\n\nfunc (s *stream) CloseSend(ctx context.Context) error {\n\tif err := s.Send(ctx, NewMessage(closeSend, nil)); err != nil {\n\t\tif ctx.Err() == nil && !(errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed)) {\n\t\t\treturn fmt.Errorf(\"send of closeSend message failed: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/tunnel/stream_conn.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// NewStreamConn returns a net.Conn that reads and writes messages from the given stream.\n// The read and write probes are optional.\nfunc NewStreamConn(ctx context.Context, s Stream, readProbe, writeProbe *CounterProbe) net.Conn {\n\treturn &streamConn{\n\t\tctx:           ctx,\n\t\tstream:        s,\n\t\treadProbe:     readProbe,\n\t\twriteProbe:    writeProbe,\n\t\treadCancelCh:  make(chan struct{}),\n\t\twriteCancelCh: make(chan struct{}),\n\t}\n}\n\ntype streamConn struct {\n\tctx           context.Context\n\tstream        Stream\n\treadCancelCh  chan struct{}\n\twriteCancelCh chan struct{}\n\treadProbe     *CounterProbe\n\twriteProbe    *CounterProbe\n\n\t// The lastIncoming message and the offset into it are protected by readLock.\n\treadLock     sync.Mutex\n\toffset       int\n\tlastIncoming Message\n\n\t// Using atomic.LoadInt64/atomic.StoreInt64 to avoid locking\n\treadDeadline  int64\n\twriteDeadline int64\n}\n\nfunc (c *streamConn) readMore() (err error) {\n\tc.offset = 0\n\tctx := c.ctx\n\tif rdl := atomic.LoadInt64(&c.readDeadline); rdl != 0 {\n\t\tdl := time.Unix(0, rdl)\n\t\tif dl.Before(time.Now()) {\n\t\t\treturn context.DeadlineExceeded\n\t\t}\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithDeadline(ctx, dl)\n\t\tdefer cancel()\n\t}\n\n\t// We must have a separate goroutine to do the read because cancelling the context is not respected by the grpc Recv function.\n\ttype msgAndErr struct {\n\t\tmsg Message\n\t\terr error\n\t}\n\tmsgCh := make(chan msgAndErr, 1)\n\tgo func() {\n\t\tvar me msgAndErr\n\t\tme.msg, me.err = c.stream.Receive(ctx)\n\t\tmsgCh <- me\n\t}()\n\tselect {\n\tcase <-ctx.Done():\n\t\terr = ctx.Err()\n\tcase <-c.readCancelCh:\n\t\terr = context.DeadlineExceeded\n\tcase me := <-msgCh:\n\t\tc.lastIncoming = me.msg\n\t\terr = me.err\n\t}\n\tif err != nil && strings.Contains(err.Error(), \"use of closed network connection\") {\n\t\terr = io.EOF\n\t}\n\treturn err\n}\n\nfunc (c *streamConn) Read(data []byte) (bytesRead int, err error) {\n\tbytesRead = 0\n\tc.readLock.Lock()\n\tdefer func() {\n\t\tif c.readProbe != nil && bytesRead > 0 {\n\t\t\tc.readProbe.Increment(uint64(bytesRead))\n\t\t}\n\t\tc.readLock.Unlock()\n\t}()\n\n\tif c.lastIncoming == nil {\n\t\terr = c.readMore()\n\t\tif err != nil {\n\t\t\treturn bytesRead, err\n\t\t}\n\t}\n\tpl := c.lastIncoming.Payload()\n\tif len(pl)-c.offset <= 0 {\n\t\terr = c.readMore()\n\t\tif err != nil {\n\t\t\treturn bytesRead, err\n\t\t}\n\t\tpl = c.lastIncoming.Payload()\n\t}\n\tbytesCopied := copy(data[bytesRead:], pl[c.offset:])\n\tc.offset += bytesCopied\n\tbytesRead += bytesCopied\n\tif c.offset == len(pl) {\n\t\tc.lastIncoming = nil\n\t}\n\treturn bytesRead, err\n}\n\nfunc (c *streamConn) Write(b []byte) (n int, err error) {\n\tctx := c.ctx\n\tif wdl := atomic.LoadInt64(&c.writeDeadline); wdl != 0 {\n\t\tdl := time.Unix(0, wdl)\n\t\tif dl.Before(time.Now()) {\n\t\t\treturn 0, context.DeadlineExceeded\n\t\t}\n\t\tvar cancel context.CancelFunc\n\t\tctx, cancel = context.WithDeadline(ctx, dl)\n\t\tdefer cancel()\n\t}\n\terrCh := make(chan error, 1)\n\tgo func() {\n\t\terrCh <- c.stream.Send(ctx, NewMessage(Normal, b))\n\t}()\n\tselect {\n\tcase <-ctx.Done():\n\t\terr = ctx.Err()\n\tcase <-c.writeCancelCh:\n\t\terr = context.DeadlineExceeded\n\tcase err = <-errCh:\n\t}\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn = len(b)\n\tif c.writeProbe != nil {\n\t\tc.writeProbe.Increment(uint64(n))\n\t}\n\tclog.Debugf(c.ctx, \"Write %d bytes\", n)\n\treturn n, nil\n}\n\nfunc (c *streamConn) Close() error {\n\tselect {\n\tcase <-c.readCancelCh:\n\t\t// Already closed.\n\t\treturn nil\n\tdefault:\n\t}\n\tclose(c.readCancelCh)\n\tclose(c.writeCancelCh)\n\treturn c.stream.CloseSend(c.ctx)\n}\n\nfunc addFromAP(ap netip.AddrPort, proto types.Proto) net.Addr {\n\tif proto == types.ProtoUDP {\n\t\treturn net.UDPAddrFromAddrPort(ap)\n\t}\n\treturn net.TCPAddrFromAddrPort(ap)\n}\n\nfunc (c *streamConn) LocalAddr() net.Addr {\n\tid := c.stream.ID()\n\treturn addFromAP(id.Source(), id.Protocol())\n}\n\nfunc (c *streamConn) RemoteAddr() net.Addr {\n\tid := c.stream.ID()\n\treturn addFromAP(id.Destination(), id.Protocol())\n}\n\nfunc (c *streamConn) SetDeadline(t time.Time) error {\n\terr := c.SetReadDeadline(t)\n\tif err == nil {\n\t\terr = c.SetWriteDeadline(t)\n\t}\n\treturn err\n}\n\nfunc (c *streamConn) SetReadDeadline(t time.Time) error {\n\tvar un int64 = 0\n\tif !t.IsZero() {\n\t\tun = t.UnixNano()\n\t}\n\tatomic.StoreInt64(&c.readDeadline, un)\n\tselect {\n\tcase c.readCancelCh <- struct{}{}:\n\tdefault:\n\t}\n\treturn nil\n}\n\nfunc (c *streamConn) SetWriteDeadline(t time.Time) error {\n\tvar un int64 = 0\n\tif !t.IsZero() {\n\t\tun = t.UnixNano()\n\t}\n\tatomic.StoreInt64(&c.writeDeadline, un)\n\tselect {\n\tcase c.writeCancelCh <- struct{}{}:\n\tdefault:\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/tunnel/stream_conn_test.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"net/netip\"\n\t\"testing\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"golang.org/x/net/nettest\"\n\t\"golang.org/x/sync/errgroup\"\n\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\n// TestStreamConn uses nettest.TestConn to test the StreamConn implementation.\nfunc TestStreamConn(t *testing.T) {\n\tnettest.TestConn(t, func() (c1, c2 net.Conn, stop func(), err error) {\n\t\tctx, stop := context.WithCancel(testutil.NewContext(t, false))\n\t\ttunnel := newBidi(1, ctx)\n\t\tlocalAddr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 1001)\n\t\tremoteAddr := netip.AddrPortFrom(netip.AddrFrom4([4]byte{192, 168, 0, 1}), 8080)\n\t\tid := NewConnID(types.ProtoTCP, localAddr, remoteAddr)\n\t\tsi := SessionID(uuid.New().String())\n\n\t\tg, _ := errgroup.WithContext(ctx)\n\t\tg.Go(func() error {\n\t\t\tclient, err := NewClientStream(ctx, ClientToManager, tunnel.clientSide(), id, si, 0, 0)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tassert.Equal(t, Version, client.PeerVersion())\n\t\t\tc1 = NewStreamConn(ctx, client, nil, nil)\n\t\t\treturn nil\n\t\t})\n\t\tg.Go(func() error {\n\t\t\tserver, err := NewServerStream(ctx, ManagerToClient, tunnel.serverSide())\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tassert.Equal(t, Version, server.PeerVersion())\n\t\t\tassert.Equal(t, si, server.SessionID())\n\t\t\tc2 = NewStreamConn(ctx, server, nil, nil)\n\t\t\treturn nil\n\t\t})\n\t\terr = g.Wait()\n\t\tif err != nil {\n\t\t\tstop()\n\t\t\treturn nil, nil, nil, err\n\t\t}\n\t\treturn c1, c2, stop, nil\n\t})\n}\n"
  },
  {
    "path": "pkg/tunnel/stream_test.go",
    "content": "package tunnel\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/telepresenceio/clog/testutil\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\ntype uni struct {\n\tctx context.Context\n\tch  chan *manager.TunnelMessage\n}\n\ntype bidi struct {\n\tcToS *uni\n\tsToC *uni\n}\n\nfunc newUni(bufSize int, ctx context.Context) *uni {\n\treturn &uni{ch: make(chan *manager.TunnelMessage, bufSize), ctx: ctx}\n}\n\nfunc newBidi(bufSize int, ctx context.Context) *bidi {\n\treturn &bidi{cToS: newUni(bufSize, ctx), sToC: newUni(bufSize, ctx)}\n}\n\nfunc (t *uni) recv(ctx context.Context) (m *manager.TunnelMessage, err error) {\n\tselect {\n\tcase <-ctx.Done():\n\t\terr = ctx.Err()\n\tcase m = <-t.ch:\n\t\tif m == nil {\n\t\t\terr = net.ErrClosed\n\t\t}\n\t}\n\treturn m, err\n}\n\nfunc (t *uni) send(ctx context.Context, msg *manager.TunnelMessage) (err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = net.ErrClosed\n\t\t}\n\t}()\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn ctx.Err()\n\tcase t.ch <- msg:\n\t\treturn nil\n\t}\n}\n\nfunc (t *uni) close() error {\n\tclose(t.ch)\n\treturn nil\n}\n\nfunc (t *bidi) clientSide() GRPCClientStream {\n\treturn &clientSide{t}\n}\n\nfunc (t *bidi) serverSide() GRPCStream {\n\treturn &serverSide{t}\n}\n\ntype clientSide struct {\n\t*bidi\n}\n\nfunc (c *clientSide) Recv() (*manager.TunnelMessage, error) {\n\treturn c.RecvContext(c.sToC.ctx)\n}\n\nfunc (c *clientSide) Send(msg *manager.TunnelMessage) error {\n\treturn c.SendContext(c.cToS.ctx, msg)\n}\n\nfunc (c *clientSide) RecvContext(ctx context.Context) (*manager.TunnelMessage, error) {\n\treturn c.sToC.recv(ctx)\n}\n\nfunc (c *clientSide) SendContext(ctx context.Context, msg *manager.TunnelMessage) error {\n\treturn c.cToS.send(ctx, msg)\n}\n\nfunc (c *clientSide) CloseSend() error {\n\treturn c.cToS.close()\n}\n\ntype serverSide struct {\n\t*bidi\n}\n\nfunc (c *serverSide) Recv() (*manager.TunnelMessage, error) {\n\treturn c.RecvContext(c.cToS.ctx)\n}\n\nfunc (c *serverSide) Send(msg *manager.TunnelMessage) error {\n\treturn c.SendContext(c.sToC.ctx, msg)\n}\n\nfunc (c *serverSide) RecvContext(ctx context.Context) (*manager.TunnelMessage, error) {\n\treturn c.cToS.recv(ctx)\n}\n\nfunc (c *serverSide) SendContext(ctx context.Context, msg *manager.TunnelMessage) error {\n\treturn c.sToC.send(ctx, msg)\n}\n\nfunc testContext(t *testing.T, timeout time.Duration) (context.Context, context.CancelFunc) {\n\treturn context.WithTimeout(testutil.NewContext(t, false), timeout)\n}\n\nfunc TestStream_Connect(t *testing.T) {\n\tctx, cancel := testContext(t, time.Second)\n\tdefer cancel()\n\n\ttunnel := newBidi(10, ctx)\n\tid := NewConnID(types.ProtoTCP, netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 1001), netip.AddrPortFrom(netip.AddrFrom4([4]byte{192, 168, 0, 1}), 8080))\n\tsi := SessionID(uuid.New().String())\n\n\twg := sync.WaitGroup{}\n\twg.Add(2)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tclient, err := NewClientStream(ctx, ClientToManager, tunnel.clientSide(), id, si, 0, 0)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, Version, client.PeerVersion())\n\t\tassert.NoError(t, client.CloseSend(ctx))\n\t}()\n\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tserver, err := NewServerStream(ctx, ManagerToClient, tunnel.serverSide())\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, id, server.ID())\n\t\tassert.Equal(t, Version, server.PeerVersion())\n\t\tassert.Equal(t, si, server.SessionID())\n\t}()\n\twg.Wait()\n}\n\nfunc produce(ctx context.Context, s Stream, msg Message, errs chan<- error) {\n\twrCh := make(chan Message)\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tWriteLoop(ctx, s, wrCh, &wg, nil)\n\tgo func() {\n\t\tfor i := 0; i < 100; i++ {\n\t\t\twrCh <- msg\n\t\t}\n\t\tclose(wrCh)\n\t\twg.Wait()\n\t}()\n\n\trdCh, errCh := ReadLoop(ctx, s, nil)\n\tselect {\n\tcase <-ctx.Done():\n\t\terrs <- ctx.Err()\n\tcase err, ok := <-errCh:\n\t\tif ok {\n\t\t\terrs <- err\n\t\t}\n\tcase m, ok := <-rdCh:\n\t\tif ok {\n\t\t\terrs <- fmt.Errorf(\"unexpected message: %s\", m)\n\t\t}\n\t}\n}\n\nfunc consume(ctx context.Context, s Stream, expectedPayload []byte, errs chan<- error) {\n\tcount := 0\n\twrCh := make(chan Message)\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tWriteLoop(ctx, s, wrCh, &wg, nil)\n\tdefer close(wrCh)\n\trdCh, errCh := ReadLoop(ctx, s, nil)\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\terrs <- ctx.Err()\n\t\tcase err, ok := <-errCh:\n\t\t\tif ok {\n\t\t\t\terrs <- err\n\t\t\t}\n\t\tcase m, ok := <-rdCh:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif m.Code() != Normal {\n\t\t\t\terrs <- fmt.Errorf(\"unexpected message code %s\", m.Code())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !bytes.Equal(expectedPayload, m.Payload()) {\n\t\t\t\terrs <- errors.New(\"unexpected message content\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcount++\n\t\t}\n\t}\n}\n\nfunc requireNoErrs(t *testing.T, errs chan error) chan error {\n\tt.Helper()\n\tclose(errs)\n\tfor err := range errs {\n\t\tassert.NoError(t, err)\n\t}\n\tif t.Failed() {\n\t\tt.FailNow()\n\t}\n\treturn make(chan error, 10)\n}\n\nfunc TestStream_Xfer(t *testing.T) {\n\tctx, cancel := testContext(t, 30*time.Second)\n\tdefer cancel()\n\n\tid := NewConnID(types.ProtoTCP, netip.AddrPortFrom(netip.AddrFrom4([4]byte{127, 0, 0, 1}), 1001), netip.AddrPortFrom(netip.AddrFrom4([4]byte{192, 168, 0, 1}), 8080))\n\tsi := SessionID(uuid.New().String())\n\tb := make([]byte, 0x1000)\n\tfor i := range b {\n\t\tb[i] = byte(i & 0xff)\n\t}\n\tlarge := NewMessage(Normal, b)\n\terrs := make(chan error, 10)\n\n\t// Send data from client to server\n\tt.Run(\"client to server\", func(t *testing.T) {\n\t\ttunnel := newBidi(10, ctx)\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(2)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif client, err := NewClientStream(ctx, ClientToManager, tunnel.clientSide(), id, si, 0, 0); err != nil {\n\t\t\t\terrs <- err\n\t\t\t} else {\n\t\t\t\tproduce(ctx, client, large, errs)\n\t\t\t}\n\t\t}()\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif server, err := NewServerStream(ctx, ManagerToClient, tunnel.serverSide()); err != nil {\n\t\t\t\terrs <- err\n\t\t\t} else {\n\t\t\t\tconsume(ctx, server, b, errs)\n\t\t\t}\n\t\t}()\n\t\twg.Wait()\n\t\terrs = requireNoErrs(t, errs)\n\t})\n\n\tt.Run(\"server to client\", func(t *testing.T) {\n\t\ttunnel := newBidi(10, ctx)\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(2)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif server, err := NewServerStream(ctx, ManagerToClient, tunnel.serverSide()); err != nil {\n\t\t\t\terrs <- err\n\t\t\t} else {\n\t\t\t\tproduce(ctx, server, large, errs)\n\t\t\t}\n\t\t}()\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tif client, err := NewClientStream(ctx, ClientToManager, tunnel.clientSide(), id, si, 0, 0); err != nil {\n\t\t\t\terrs <- err\n\t\t\t} else {\n\t\t\t\tconsume(ctx, client, b, errs)\n\t\t\t}\n\t\t}()\n\t\twg.Wait()\n\t\terrs = requireNoErrs(t, errs)\n\t})\n}\n"
  },
  {
    "path": "pkg/tunnel/synthetic.go",
    "content": "package tunnel\n\nimport (\n\t\"net/netip\"\n)\n\ntype SyntheticIPResolver interface {\n\tResolve(netip.Addr) (netip.Addr, error)\n\tResolveName(netip.Addr) string\n}\n"
  },
  {
    "path": "pkg/tunnel/timed_handler.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n)\n\ntype TimedHandler struct {\n\tID        ConnID\n\tttl       time.Duration\n\tremove    func()\n\tidleTimer *time.Timer\n\tidleLock  sync.Mutex\n}\n\nfunc NewTimedHandler(id ConnID, ttl time.Duration, remove func()) TimedHandler {\n\treturn TimedHandler{ID: id, ttl: ttl, remove: remove}\n}\n\nfunc (h *TimedHandler) Idle() <-chan time.Time {\n\treturn h.idleTimer.C\n}\n\nfunc (h *TimedHandler) GetTTL() time.Duration {\n\th.idleLock.Lock()\n\tttl := h.ttl\n\th.idleLock.Unlock()\n\treturn ttl\n}\n\nfunc (h *TimedHandler) SetTTL(ttl time.Duration) {\n\th.idleLock.Lock()\n\th.ttl = ttl\n\th.idleLock.Unlock()\n}\n\nfunc (h *TimedHandler) ResetIdle() bool {\n\th.idleLock.Lock()\n\tstopped := h.idleTimer.Stop()\n\tif stopped {\n\t\th.idleTimer.Reset(h.ttl)\n\t}\n\th.idleLock.Unlock()\n\treturn stopped\n}\n\nfunc (h *TimedHandler) Start(_ context.Context) {\n\th.idleLock.Lock()\n\th.idleTimer = time.NewTimer(h.ttl)\n\th.idleLock.Unlock()\n}\n\nfunc (h *TimedHandler) Stop(_ context.Context) {\n\th.idleLock.Lock()\n\tif h.remove != nil {\n\t\th.remove()\n\t\th.remove = nil\n\t\tif h.idleTimer != nil {\n\t\t\th.idleTimer.Stop()\n\t\t}\n\t}\n\th.idleLock.Unlock()\n}\n"
  },
  {
    "path": "pkg/tunnel/udplistener.go",
    "content": "package tunnel\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/netip\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\n// The dialer takes care of dispatching messages between gRPC and UDP connections.\ntype udpListener struct {\n\tTimedHandler\n\ttag        Tag\n\tconn       *net.UDPConn\n\tconnected  int32\n\tdone       chan struct{}\n\ttargetAddr netip.AddrPort\n\ttargets    *Pool\n\tcreator    func(context.Context, ConnID) (Stream, error)\n}\n\nfunc NewUDPListener(conn *net.UDPConn, tag Tag, targetAddr netip.AddrPort, creator func(context.Context, ConnID) (Stream, error)) Endpoint {\n\tstate := notConnected\n\tif conn != nil {\n\t\tstate = connecting\n\t}\n\treturn &udpListener{\n\t\tTimedHandler: NewTimedHandler(\"\", udpConnTTL, nil),\n\t\ttag:          tag,\n\t\tconn:         conn,\n\t\tconnected:    state,\n\t\tdone:         make(chan struct{}),\n\t\ttargetAddr:   targetAddr,\n\t\ttargets:      NewPool(),\n\t\tcreator:      creator,\n\t}\n}\n\nfunc (h *udpListener) Start(ctx context.Context) {\n\th.TimedHandler.Start(ctx)\n\tgo func() {\n\t\tdefer close(h.done)\n\n\t\th.connected = connected\n\t\th.connToStreamLoop(ctx)\n\t\th.Stop(ctx)\n\t}()\n}\n\nfunc (h *udpListener) connToStreamLoop(ctx context.Context) {\n\tch := make(chan UdpReadResult)\n\tgo UdpReader(ctx, h.tag, h.conn, ch)\n\tfor atomic.LoadInt32(&h.connected) == connected {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-h.Idle():\n\t\t\treturn\n\t\tcase rr, ok := <-ch:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\th.ResetIdle()\n\t\t\tid := ConnIDFromUDP(rr.Address, h.targetAddr)\n\t\t\ttarget, _, err := h.targets.GetOrCreate(ctx, id, func(ctx context.Context, release func()) (Handler, error) {\n\t\t\t\ts, err := h.creator(ctx, id)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tclog.Debugf(ctx, \"-> %s %s conn-to-stream loop started\", h.tag, id)\n\t\t\t\treturn &udpStream{\n\t\t\t\t\tTimedHandler: NewTimedHandler(id, udpConnTTL, release),\n\t\t\t\t\tudpListener:  h,\n\t\t\t\t\tstream:       s,\n\t\t\t\t}, nil\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tclog.Errorf(ctx, \">! %s udp %s get target: %v\", h.tag, id, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tps := target.(*udpStream)\n\t\t\tclog.Tracef(ctx, \"-> %s %s, len %d\", h.tag, id, len(rr.Payload))\n\t\t\terr = ps.stream.Send(ctx, NewMessage(Normal, rr.Payload))\n\t\t\tif err != nil {\n\t\t\t\tclog.Errorf(ctx, \">! %s udp %s write: %v\", h.tag, id, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (h *udpListener) Done() <-chan struct{} {\n\treturn h.done\n}\n\ntype udpStream struct {\n\tTimedHandler\n\t*udpListener\n\tstream Stream\n}\n\nfunc (p *udpStream) getStream() Stream {\n\treturn p.stream\n}\n\nfunc (p *udpStream) reply(data []byte) (int, error) {\n\treturn p.conn.WriteTo(data, net.UDPAddrFromAddrPort(p.ID.Source()))\n}\n\nfunc (p *udpStream) startDisconnect(context.Context, string, bool) {\n}\n\nfunc (p *udpStream) Stop(ctx context.Context) {\n\t_ = p.stream.CloseSend(ctx)\n}\n\nfunc (p *udpStream) Start(ctx context.Context) {\n\tp.TimedHandler.Start(ctx)\n\tgo readLoop(ctx, p.stream.Tag(), p, nil)\n}\n\ntype UdpReadResult struct {\n\tPayload []byte\n\tAddress netip.AddrPort\n}\n\n// IsTimeout returns true if the given error is a network timeout error.\nfunc IsTimeout(err error) bool {\n\tvar opErr *net.OpError\n\treturn errors.As(err, &opErr) && opErr.Timeout()\n}\n\n// UdpReader continuously reads from a net.PacketConn and writes the resulting payload and\n// reply address to a channel. The loop is canceled when the connection is closed or when\n// the context is done, at which time the channel is closed.\nfunc UdpReader(ctx context.Context, tag Tag, conn net.PacketConn, ch chan<- UdpReadResult) {\n\tdefer close(ch)\n\tvar endReason string\n\tendLevel := clog.LevelTrace\n\tdefer func() {\n\t\tclog.Logf(ctx, endLevel, \"<- %s %s UDP read loop ended because %s\", tag, conn.LocalAddr(), endReason)\n\t}()\n\tbuf := [0x10000]byte{}\n\tfor {\n\t\tif err := ctx.Err(); err != nil {\n\t\t\tendReason = err.Error()\n\t\t\tbreak\n\t\t}\n\t\t_ = conn.SetReadDeadline(time.Now().Add(time.Second))\n\t\tn, addr, err := conn.ReadFrom(buf[:])\n\t\tif n > 0 {\n\t\t\tpl := make([]byte, n)\n\t\t\tcopy(pl, buf[:n])\n\t\t\tch <- UdpReadResult{pl, addr.(*net.UDPAddr).AddrPort()}\n\t\t}\n\t\tswitch {\n\t\tcase err == nil, IsTimeout(err):\n\t\t\tcontinue\n\t\tcase errors.Is(err, io.EOF):\n\t\t\tendReason = \"EOF was encountered\"\n\t\tcase errors.Is(err, net.ErrClosed):\n\t\t\tendReason = \"the connection was closed\"\n\t\tdefault:\n\t\t\tendReason = fmt.Sprintf(\"a read error occurred: %v\", err)\n\t\t\tendLevel = slog.LevelError\n\t\t}\n\t\tbreak\n\t}\n}\n"
  },
  {
    "path": "pkg/types/addrportandproto.go",
    "content": "package types\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n)\n\ntype AddrPortProto struct {\n\tnetip.AddrPort\n\tProto Proto\n}\n\nfunc ParseAddrPortProto(pm string) (pap AddrPortProto, err error) {\n\t// Parse backwards. An IPv6 address contains colons.\n\tpap.Proto = ProtoTCP\n\tif ix := strings.LastIndexByte(pm, ProtoSeparator); ix > 0 {\n\t\tpap.Proto, err = ParseProto(pm[ix+1:])\n\t\tif err != nil {\n\t\t\treturn pap, err\n\t\t}\n\t\tpm = pm[:ix]\n\t}\n\tpap.AddrPort, err = netip.ParseAddrPort(pm)\n\treturn pap, err\n}\n\nfunc (p AddrPortProto) String() string {\n\tif p.Proto == ProtoTCP {\n\t\treturn p.AddrPort.String()\n\t}\n\treturn fmt.Sprintf(\"%s/%s\", p.AddrPort, p.Proto)\n}\n\nfunc (p AddrPortProto) MarshalBinary() ([]byte, error) {\n\tbs, err := p.AddrPort.MarshalBinary()\n\tif err == nil {\n\t\tbs = append(bs, byte(p.Proto))\n\t}\n\treturn bs, err\n}\n\nfunc (p AddrPortProto) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, p.String())\n}\n\nfunc (p *AddrPortProto) UnmarshalBinary(bs []byte) error {\n\tlast := len(bs) - 1\n\tif last < 2 {\n\t\treturn errors.New(\"unexpected slice size\")\n\t}\n\terr := p.AddrPort.UnmarshalBinary(bs[:last])\n\tif err == nil {\n\t\tp.Proto = Proto(bs[last])\n\t}\n\treturn err\n}\n\nfunc (p *AddrPortProto) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\tvar s string\n\terr := json.UnmarshalDecode(in, &s)\n\tif err == nil {\n\t\t*p, err = ParseAddrPortProto(s)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/types/engagement.go",
    "content": "package types\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n)\n\ntype EngagementType int\n\nvar engagementTypes = map[string]EngagementType{ //nolint:gochecknoglobals // constant names\n\t\"connect\":   EngagementTypeConnect,\n\t\"ingest\":    EngagementTypeIngest,\n\t\"wiretap\":   EngagementTypeWiretap,\n\t\"intercept\": EngagementTypeIntercept,\n\t\"replace\":   EngagementTypeReplace,\n\t\"proxy\":     EngagementTypeProxy,\n}\n\nconst (\n\tEngagementTypeConnect = EngagementType(iota)\n\tEngagementTypeIngest\n\tEngagementTypeWiretap\n\tEngagementTypeIntercept\n\tEngagementTypeReplace\n\tEngagementTypeProxy\n)\n\nvar egStrings = [6][5]string{ //nolint:gochecknoglobals // constant names\n\t{\n\t\t\"connect\",\n\t\t\"Connecting\",\n\t\t\"Connected\",\n\t\t\"Disconnecting\",\n\t\t\"Disconnected\",\n\t},\n\t{\n\t\t\"ingest\",\n\t\t\"Ingesting\",\n\t\t\"Ingested\",\n\t\t\"Leaving ingest\",\n\t\t\"Left ingest\",\n\t},\n\t{\n\t\t\"wiretap\",\n\t\t\"Wiretapping\",\n\t\t\"Wiretapped\",\n\t\t\"Removing wiretap\",\n\t\t\"Removed wiretap\",\n\t},\n\t{\n\t\t\"intercept\",\n\t\t\"Intercepting\",\n\t\t\"Intercepted\",\n\t\t\"Leaving intercept\",\n\t\t\"Left intercept\",\n\t},\n\t{\n\t\t\"replace\",\n\t\t\"Replacing\",\n\t\t\"Replaced\",\n\t\t\"Restoring\",\n\t\t\"Restored\",\n\t},\n\t{\n\t\t\"proxy\",\n\t\t\"Proxying\",\n\t\t\"Proxied\",\n\t\t\"Removing proxy\",\n\t\t\"Removed proxy\",\n\t},\n}\n\nconst invalidType = \"invalid engagement type %s\"\n\nfunc ParseEngagementType(s string) (EngagementType, error) {\n\tif e, ok := engagementTypes[strings.ToLower(s)]; ok {\n\t\treturn e, nil\n\t}\n\treturn 0, fmt.Errorf(invalidType, s)\n}\n\nfunc EngagementTypeFromSpec(spec *manager.InterceptSpec) EngagementType {\n\tswitch {\n\tcase spec.Wiretap:\n\t\treturn EngagementTypeWiretap\n\tcase spec.NoDefaultPort:\n\t\treturn EngagementTypeReplace\n\tdefault:\n\t\treturn EngagementTypeIntercept\n\t}\n}\n\nfunc (e EngagementType) strings() [5]string {\n\tif e >= 0 && e < 6 {\n\t\treturn egStrings[e]\n\t}\n\ten := fmt.Sprintf(invalidType, strconv.Itoa(int(e)))\n\treturn [5]string{en, en, en, en, en}\n}\n\nfunc (e EngagementType) String() string {\n\treturn e.strings()[0]\n}\n\nfunc (e EngagementType) Working() string {\n\treturn e.strings()[1]\n}\n\nfunc (e EngagementType) WorkDone() string {\n\treturn e.strings()[2]\n}\n\nfunc (e EngagementType) Leaving() string {\n\treturn e.strings()[3]\n}\n\nfunc (e EngagementType) Left() string {\n\treturn e.strings()[4]\n}\n\nfunc (e EngagementType) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, e.String())\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (e *EngagementType) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\tvar s string\n\terr := json.UnmarshalDecode(in, &s)\n\tif err == nil {\n\t\t*e, err = ParseEngagementType(s)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/types/mountpolicy.go",
    "content": "package types\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"maps\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/annotation\"\n)\n\ntype MountPolicy int\n\ntype MountPolicies map[string]MountPolicy\n\nconst (\n\t// MountPolicyRemote means that the client can (or in the case of a docker-run, will) mount the\n\t// volume using a remote file system. Unless constrained by other mechanisms, the mount will\n\t// be read-write.\n\tMountPolicyRemote MountPolicy = iota\n\n\t// MountPolicyRemoteReadOnly is like MountPolicyRemote but will enforce a read-only mount.\n\tMountPolicyRemoteReadOnly\n\n\t// MountPolicyLocal means that the mount will be confined to the workstation. This is typically\n\t// the case for /tmp.\n\tMountPolicyLocal\n\n\t// MountPolicyIgnore means that the mount will be completely ignored by Telepresence.\n\tMountPolicyIgnore\n)\n\nvar mountPolicyNames = []string{\"Remote\", \"RemoteReadOnly\", \"Local\", \"Ignore\"} //nolint:gochecknoglobals // constant\n\nfunc (mp MountPolicy) String() string {\n\tif mp >= 0 && int(mp) < len(mountPolicyNames) {\n\t\treturn mountPolicyNames[mp]\n\t}\n\treturn \"Unknown\"\n}\n\nfunc (mp MountPolicy) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, mp.String())\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (mp *MountPolicy) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\tvar s string\n\terr := json.UnmarshalDecode(in, &s)\n\tif err == nil {\n\t\t*mp, err = ParseMountPolicy(s)\n\t}\n\treturn err\n}\n\n//goland:noinspection GoMixedReceiverTypes\nfunc (mp *MountPolicy) UnmarshalText(value []byte) (err error) {\n\t*mp, err = ParseMountPolicy(string(value))\n\treturn err\n}\n\nfunc ParseMountPolicy(s string) (MountPolicy, error) {\n\tif ix := slices.IndexFunc(mountPolicyNames, func(pn string) bool {\n\t\treturn strings.EqualFold(pn, s)\n\t}); ix >= 0 {\n\t\treturn MountPolicy(ix), nil\n\t}\n\treturn MountPolicyIgnore, fmt.Errorf(\"invalid mount policy: %q\", s)\n}\n\nfunc (iv MountPolicies) AddAnnotations(ctx context.Context, annotations map[string]string) (MountPolicies, error) {\n\tignores, err := iv.getIgnoreAnnotations(ctx, annotations)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpolicies, err := iv.getPolicyAnnotations(annotations)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(ignores) == 0 && len(policies) == 0 {\n\t\treturn iv, nil\n\t}\n\tmps := maps.Clone(iv)\n\tfor key, policy := range policies {\n\t\tmps[key] = policy\n\t}\n\tfor _, key := range ignores {\n\t\tmps[key] = MountPolicyIgnore\n\t}\n\treturn mps, nil\n}\n\nfunc MountPoliciesFromRPC(mr map[string]int32) MountPolicies {\n\tif mr == nil {\n\t\treturn nil\n\t}\n\tmps := make(MountPolicies, len(mr))\n\tfor k, v := range mr {\n\t\tmps[k] = MountPolicy(v)\n\t}\n\treturn mps\n}\n\nfunc (iv MountPolicies) ToRPC() map[string]int32 {\n\tif len(iv) == 0 {\n\t\treturn nil\n\t}\n\tmr := make(map[string]int32, len(iv))\n\tfor key, policy := range iv {\n\t\tmr[key] = int32(policy)\n\t}\n\treturn mr\n}\n\nfunc (iv MountPolicies) getPolicyAnnotations(anns map[string]string) (mps MountPolicies, err error) {\n\tvma, ok := anns[annotation.VolumeMountPolicies]\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\tvma = strings.TrimSpace(vma)\n\tif len(vma) == 0 {\n\t\treturn nil, nil\n\t}\n\n\t// Unmarshalling into the clone overwrites existing entries in the clone. This is intentional. The\n\t// annotation has higher priority.\n\terr = json.Unmarshal([]byte(vma), &mps)\n\treturn mps, err\n}\n\nfunc (iv MountPolicies) getIgnoreAnnotations(ctx context.Context, anns map[string]string) (ignores []string, err error) {\n\tvma := annotation.GetAnnotation(ctx, anns, annotation.InjectIgnoreVolumeMounts, annotation.LegacyInjectIgnoreVolumeMounts)\n\tvma = strings.TrimSpace(vma)\n\tif len(vma) == 0 {\n\t\treturn nil, nil\n\t}\n\n\t// We accept two formats.\n\t// 1. A JSON []string (all entries considered to be MountPolicyIgnore)\n\t// 2. A comma separated []string (all entries considered to be MountPolicyIgnore)\n\tswitch vma[0] {\n\tcase '[':\n\t\terr = json.Unmarshal([]byte(vma), &ignores)\n\tdefault:\n\t\tignores = strings.Split(vma, \",\")\n\t\tfor i, vm := range ignores {\n\t\t\tignores[i] = strings.TrimSpace(vm)\n\t\t}\n\t}\n\treturn ignores, err\n}\n\nfunc (iv MountPolicies) Get(volumeName, mountPath string) MountPolicy {\n\tfor key, policy := range iv {\n\t\tif key == volumeName || strings.HasPrefix(mountPath, key) {\n\t\t\treturn policy\n\t\t}\n\t}\n\treturn MountPolicyRemote\n}\n"
  },
  {
    "path": "pkg/types/portandproto.go",
    "content": "package types\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n)\n\nvar ErrNotInteger = errors.New(\"not an integer\")\n\nconst ProtoSeparator = byte('/')\n\n// ParsePort parses the given string into a positive unsigned 16-bit integer.\n// ErrNotInteger is returned if the string doesn't represent an integer.\n// A range error is return unless the integer is between 1 and 65535.\nfunc ParsePort(portStr string) (uint16, error) {\n\tport, err := strconv.Atoi(portStr)\n\tif err != nil {\n\t\treturn 0, ErrNotInteger\n\t}\n\tif port < 1 || port > math.MaxUint16 {\n\t\treturn 0, fmt.Errorf(\"%s is not between 1 and 65535\", portStr)\n\t}\n\treturn uint16(port), nil\n}\n\ntype PortAndProto struct {\n\tPort  uint16\n\tProto Proto\n}\n\nfunc ParsePortAndProto(s string) (PortAndProto, error) {\n\tpp := PortAndProto{Proto: ProtoTCP}\n\tvar err error\n\tif ix := strings.IndexByte(s, ProtoSeparator); ix > 0 {\n\t\tif pp.Proto, err = ParseProto(s[ix+1:]); err != nil {\n\t\t\treturn pp, err\n\t\t}\n\t\ts = s[0:ix]\n\t}\n\tpp.Port, err = ParsePort(s)\n\treturn pp, err\n}\n\nfunc (pp *PortAndProto) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, pp.String())\n}\n\n// String will consistently yield the identifier without the protocol suffix when the protocol is TCP\n// and otherwise always use the suffix \"/UDP\".\nfunc (pp *PortAndProto) String() string {\n\tif pp.Proto == ProtoTCP {\n\t\treturn strconv.Itoa(int(pp.Port))\n\t}\n\treturn fmt.Sprintf(\"%d/%s\", pp.Port, pp.Proto)\n}\n\nfunc (pp *PortAndProto) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\tvar s string\n\terr := json.UnmarshalDecode(in, &s)\n\tif err == nil {\n\t\t*pp, err = ParsePortAndProto(s)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/types/portidentifier.go",
    "content": "package types\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n\t\"k8s.io/apimachinery/pkg/util/validation\"\n)\n\n// PortIdentifier identifies a port (service or container) unambiguously using\n// the notation <name or number>/<protocol>. A named port will always be identified\n// using the name, and the protocol will only be appended when it is not TCP.\ntype PortIdentifier string\n\n// ValidatePort validates a port string. An error is returned if the string isn't a\n// number between 1 and 65535 or a DNS_LABEL.\nfunc ValidatePort(s string) error {\n\t_, err := ParsePort(s)\n\tif err == ErrNotInteger {\n\t\terr = nil\n\t\tif errs := validation.IsDNS1035Label(s); len(errs) > 0 {\n\t\t\terr = errors.New(strings.Join(errs, \" and \"))\n\t\t}\n\t}\n\treturn err\n}\n\n// NewPortIdentifier creates a new PortIdentifier from a protocol and a string that\n// is either a name or a number. An error is returned if the protocol is unsupported,\n// if a port number is not between 1 and 65535, or if the name isn't a DNS_LABEL.\nfunc NewPortIdentifier(proto Proto, portString string) (PortIdentifier, error) {\n\tif err := ValidatePort(portString); err != nil {\n\t\treturn \"\", err\n\t}\n\tif proto != ProtoTCP {\n\t\tportString += string([]byte{ProtoSeparator}) + proto.String()\n\t}\n\treturn PortIdentifier(portString), nil\n}\n\n// HasProto returns the protocol, and the name or number.\nfunc (spi PortIdentifier) HasProto() bool {\n\treturn strings.IndexByte(string(spi), ProtoSeparator) > 0\n}\n\n// Validate checks that the PortIdentifier has a valid protocol, and a valid name or number.\nfunc (spi PortIdentifier) Validate() (err error) {\n\ts := string(spi)\n\tif ix := strings.IndexByte(s, ProtoSeparator); ix > 0 {\n\t\t_, err = ParseProto(s[ix+1:])\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts = s[0:ix]\n\t}\n\treturn ValidatePort(s)\n}\n\n// ProtoAndNameOrNumber returns the protocol, and the name or number.\nfunc (spi PortIdentifier) ProtoAndNameOrNumber() (Proto, string, uint16) {\n\ts := string(spi)\n\tp := ProtoTCP\n\tif ix := strings.IndexByte(s, ProtoSeparator); ix > 0 {\n\t\tp, _ = ParseProto(s[ix+1:])\n\t\ts = s[0:ix]\n\t}\n\tif n, err := ParsePort(s); err == nil {\n\t\treturn p, \"\", n\n\t}\n\treturn p, s, 0\n}\n\nfunc (spi PortIdentifier) AsIntOrStr() intstr.IntOrString {\n\t_, s, n := spi.ProtoAndNameOrNumber()\n\tif s == \"\" {\n\t\treturn intstr.FromInt32(int32(n))\n\t}\n\treturn intstr.FromString(s)\n}\n\n// String will consistently yield the identifier without the protocol suffix when the protocol is TCP\n// and otherwise always use the suffix \"/UDP\".\nfunc (spi PortIdentifier) String() string {\n\tp, s, n := spi.ProtoAndNameOrNumber()\n\tswitch {\n\tcase s == \"\" && p == ProtoTCP:\n\t\treturn strconv.Itoa(int(n))\n\tcase s != \"\" && p == ProtoTCP:\n\t\treturn s\n\tcase s == \"\":\n\t\treturn fmt.Sprintf(\"%d/%s\", n, p)\n\tdefault:\n\t\treturn fmt.Sprintf(\"%s/%s\", s, p)\n\t}\n}\n"
  },
  {
    "path": "pkg/types/portmapping.go",
    "content": "package types\n\nimport (\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n\t\"k8s.io/apimachinery/pkg/util/intstr\"\n)\n\ntype PortMapping string\n\nfunc NewPortMapping(from PortIdentifier, to uint16) PortMapping {\n\tp, s, n := from.ProtoAndNameOrNumber()\n\tswitch {\n\tcase s == \"\" && p == ProtoTCP:\n\t\ts = fmt.Sprintf(\"%d:%d\", n, to)\n\tcase s == \"\":\n\t\ts = fmt.Sprintf(\"%d:%d/%s\", n, to, p)\n\tcase p == ProtoTCP:\n\t\ts = fmt.Sprintf(\"%s:%d\", s, to)\n\tdefault:\n\t\ts = fmt.Sprintf(\"%s:%d/%s\", s, to, p)\n\t}\n\treturn PortMapping(s)\n}\n\nfunc (p PortMapping) String() string {\n\treturn string(p)\n}\n\nfunc (p PortMapping) From() PortIdentifier {\n\tfrom, _, _ := p.FromAndTo()\n\treturn from\n}\n\nfunc (p PortMapping) FromAsNumeric() PortAndProto {\n\tpr, _, n := p.From().ProtoAndNameOrNumber()\n\treturn PortAndProto{\n\t\tPort:  n,\n\t\tProto: pr,\n\t}\n}\n\nfunc (p PortMapping) FromAsIntOrStr() intstr.IntOrString {\n\treturn p.From().AsIntOrStr()\n}\n\nfunc (p PortMapping) To() PortIdentifier {\n\t_, toAndProto, _ := p.FromAndTo()\n\treturn toAndProto\n}\n\nfunc (p PortMapping) ToAsNumeric() PortAndProto {\n\tpr, _, n := p.To().ProtoAndNameOrNumber()\n\treturn PortAndProto{\n\t\tPort:  n,\n\t\tProto: pr,\n\t}\n}\n\nfunc (p PortMapping) ToAsIntOrStr() intstr.IntOrString {\n\treturn p.To().AsIntOrStr()\n}\n\nfunc (p PortMapping) Validate() error {\n\t_, _, err := p.FromAndTo()\n\treturn err\n}\n\nfunc (p *PortMapping) UnmarshalJSONFrom(in *jsontext.Decoder) error {\n\tvar s string\n\terr := json.UnmarshalDecode(in, &s)\n\tif err == nil {\n\t\tpm := PortMapping(s)\n\t\terr = pm.Validate()\n\t\tif err == nil {\n\t\t\t*p = pm\n\t\t}\n\t}\n\treturn err\n}\n\n// FromAndTo returns the identifier for the source port and the PortAndProto of the destination port.\n// An error is returned if the port-mapping syntax is invalid.\nfunc (p PortMapping) FromAndTo() (from PortIdentifier, to PortIdentifier, err error) {\n\tps := string(p)\n\tif sepIdx := strings.IndexByte(ps, ':'); sepIdx > 0 {\n\t\tto = PortIdentifier(ps[sepIdx+1:])\n\t\terr = to.Validate()\n\t\tif err == nil {\n\t\t\tproto, _, _ := to.ProtoAndNameOrNumber()\n\t\t\tfrom, err = NewPortIdentifier(proto, ps[:sepIdx])\n\t\t}\n\t} else {\n\t\tto = PortIdentifier(ps)\n\t\terr = to.Validate()\n\t\tif err == nil {\n\t\t\tfrom = PortIdentifier(ps)\n\t\t\terr = from.Validate()\n\t\t}\n\t}\n\treturn from, to, err\n}\n\n// FromNumberAndTo returns source port number and the PortAndProto of the destination port.\n// An error is returned if the source port is symbolic or if the port-mapping syntax is invalid.\nfunc (p PortMapping) FromNumberAndTo() (from uint16, to PortAndProto, err error) {\n\tps := string(p)\n\tif sepIdx := strings.IndexByte(ps, ':'); sepIdx > 0 {\n\t\tto, err = ParsePortAndProto(ps[sepIdx+1:])\n\t\tif err == nil {\n\t\t\tvar fi uint64\n\t\t\tfi, err = strconv.ParseUint(ps[:sepIdx], 10, 16)\n\t\t\tfrom = uint16(fi)\n\t\t}\n\t} else {\n\t\tto, err = ParsePortAndProto(ps)\n\t\tif err == nil {\n\t\t\tfrom = to.Port\n\t\t}\n\t}\n\treturn from, to, err\n}\n"
  },
  {
    "path": "pkg/types/proto.go",
    "content": "package types\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/go-json-experiment/json\"\n\t\"github.com/go-json-experiment/json/jsontext\"\n\tcore \"k8s.io/api/core/v1\"\n)\n\n// Proto declares the IP protocol numbers.\n// See: https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml\ntype Proto byte\n\nconst (\n\tProtoTCP    = Proto(6)\n\tProtoUDP    = Proto(17)\n\tProtoICMP   = Proto(1)\n\tProtoICMPV6 = Proto(58)\n\tProtoSCTP   = Proto(132)\n)\n\n// ParseProto returns the IP protocol for the given network. Currently only supports\n// TCP, UDP, and ICMP.\nfunc ParseProto(network string) (Proto, error) {\n\tswitch strings.ToLower(network) {\n\tcase \"\", \"tcp\", \"tcp4\", \"tcp6\":\n\t\treturn ProtoTCP, nil\n\tcase \"udp\", \"udp4\", \"udp6\":\n\t\treturn ProtoUDP, nil\n\tcase \"icmp\":\n\t\treturn ProtoICMP, nil\n\tcase \"icmpv6\":\n\t\treturn ProtoICMPV6, nil\n\tcase \"sctp\":\n\t\treturn ProtoSCTP, nil\n\tdefault:\n\t\treturn 0, fmt.Errorf(\"unsupported protocol: %q\", network)\n\t}\n}\n\nfunc FromK8sProtocol(protocol core.Protocol) Proto {\n\tp, err := ParseProto(string(protocol))\n\tif err != nil {\n\t\tp = ProtoTCP\n\t}\n\treturn p\n}\n\nfunc (p Proto) String() string {\n\tswitch p {\n\tcase ProtoICMP:\n\t\treturn \"ICMP\"\n\tcase ProtoICMPV6:\n\t\treturn \"ICMPv6\"\n\tcase 0, ProtoTCP:\n\t\treturn string(core.ProtocolTCP)\n\tcase ProtoUDP:\n\t\treturn string(core.ProtocolUDP)\n\tcase ProtoSCTP:\n\t\treturn string(core.ProtocolSCTP)\n\tdefault:\n\t\treturn fmt.Sprintf(\"IP-protocol %d\", p)\n\t}\n}\n\nfunc (p Proto) MarshalJSONTo(out *jsontext.Encoder) error {\n\treturn json.MarshalEncode(out, p.String())\n}\n\nfunc (p *Proto) UnmarshalJSONFrom(in *jsontext.Decoder) (err error) {\n\tif in.PeekKind() == '0' {\n\t\t// Backwards compatibility. Older versions uses the protocol number in JSON.\n\t\tvar bn byte\n\t\terr = json.UnmarshalDecode(in, &bn)\n\t\tif err == nil {\n\t\t\t*p = Proto(bn)\n\t\t}\n\t} else {\n\t\tvar s string\n\t\terr = json.UnmarshalDecode(in, &s)\n\t\tif err == nil {\n\t\t\t*p, err = ParseProto(s)\n\t\t}\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "pkg/version/version.go",
    "content": "package version\n\nimport (\n\t\"os\"\n\t\"runtime/debug\"\n\t\"strings\"\n\n\t\"github.com/blang/semver/v4\"\n)\n\n// Version is a \"vSEMVER\" string, and is either populated at build-time using `--ldflags -X`, or at\n// init()-time by inspecting the binary's own debug info.\nvar (\n\tVersion     string         //nolint:gochecknoglobals // constant\n\tHelmVersion string         //nolint:gochecknoglobals // constant\n\tStructured  semver.Version //nolint:gochecknoglobals // constant\n)\n\nfunc init() {\n\t// Prefer version number inserted at build using --ldflags, but if it's not set...\n\tVersion, Structured = Init(Version, \"TELEPRESENCE_VERSION\")\n}\n\nfunc Init(s, envKey string) (string, semver.Version) {\n\tif s == \"\" {\n\t\tif info, ok := debug.ReadBuildInfo(); ok {\n\t\t\t// Fall back to version info from \"go get\"\n\t\t\ts = info.Main.Version\n\t\t}\n\t\tif s == \"\" {\n\t\t\tif s = os.Getenv(envKey); s == \"\" {\n\t\t\t\ts = \"0.0.0-unknown\"\n\t\t\t}\n\t\t}\n\t}\n\tif s == \"(devel)\" {\n\t\ts = \"0.0.0-devel\"\n\t}\n\ts = strings.TrimPrefix(s, \"v\")\n\tsv, err := semver.Parse(s)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn \"v\" + s, sv\n}\n\nfunc GetExecutable() (string, error) {\n\texecutable, err := os.Executable()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn executable, nil\n}\n"
  },
  {
    "path": "pkg/vif/device.go",
    "content": "package vif\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n)\n\ntype Device interface {\n\tClose() // Overrides stack.LinkEndpoint.Close. Must not return error.\n\tNewLinkEndpoint() (stack.LinkEndpoint, error)\n\tIndex() uint32\n\tName() string\n\tAddSubnet(context.Context, netip.Prefix) error\n\tRemoveSubnet(context.Context, netip.Prefix) error\n\tSetDNS(context.Context, string, netip.AddrPort, []string) (err error)\n\tWaitForDevice()\n}\n\nvar _ Device = (*device)(nil)\n\n// OpenTun creates a new TUN device and ensures that it is up and running.\nfunc OpenTun(ctx context.Context) (Device, error) {\n\treturn openTun(ctx)\n}\n\n// AddSubnet adds a subnet to this TUN device and creates a route for that subnet which\n// is associated with the device (removing the device will automatically remove the route).\nfunc (d *device) AddSubnet(ctx context.Context, subnet netip.Prefix) (err error) {\n\treturn d.addSubnet(ctx, subnet)\n}\n\n// Index returns the index of this device.\nfunc (d *device) Index() uint32 {\n\treturn d.interfaceIndex\n}\n\n// Name returns the name of this device, e.g. \"tun0\".\nfunc (d *device) Name() string {\n\treturn d.name\n}\n\nfunc (d *device) NewLinkEndpoint() (stack.LinkEndpoint, error) {\n\treturn d.createLinkEndpoint()\n}\n\n// SetDNS sets the DNS configuration for the device on the windows platform.\nfunc (d *device) SetDNS(ctx context.Context, clusterDomain string, server netip.AddrPort, domains []string) (err error) {\n\treturn d.setDNS(ctx, clusterDomain, server, domains)\n}\n\n// RemoveSubnet removes a subnet from this TUN device and also removes the route for that subnet which\n// is associated with the device.\nfunc (d *device) RemoveSubnet(ctx context.Context, subnet netip.Prefix) (err error) {\n\treturn d.removeSubnet(ctx, subnet)\n}\n"
  },
  {
    "path": "pkg/vif/device_darwin.go",
    "content": "package vif\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"runtime\"\n\t\"sync\"\n\t\"unsafe\"\n\n\t\"golang.org/x/net/ipv4\"\n\t\"golang.org/x/net/ipv6\"\n\t\"golang.org/x/sys/unix\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/link/channel\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/routing\"\n)\n\nconst (\n\tsysProtoControl = 2\n\tuTunOptIfName   = 2\n\tuTunControlName = \"com.apple.net.utun_control\"\n)\n\ntype device struct {\n\t*channel.Endpoint\n\tfile           *os.File\n\tctx            context.Context\n\tname           string\n\tinterfaceIndex uint32\n\twb             bytes.Buffer\n\twg             sync.WaitGroup\n}\n\nfunc openTun(ctx context.Context) (*device, error) {\n\tfd, err := unix.Socket(unix.AF_SYSTEM, unix.SOCK_DGRAM, sysProtoControl)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open DGRAM socket: %w\", err)\n\t}\n\tunix.CloseOnExec(fd)\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = unix.Close(fd)\n\t\t}\n\t}()\n\n\tinfo := &unix.CtlInfo{}\n\tcopy(info.Name[:], uTunControlName)\n\tif err = unix.IoctlCtlInfo(fd, info); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to getBuffer IOCTL info for %s: %w\", uTunControlName, err)\n\t}\n\n\tif err = unix.Connect(fd, &unix.SockaddrCtl{ID: info.Id, Unit: 0}); err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err = unix.SetNonblock(fd, true); err != nil {\n\t\treturn nil, err\n\t}\n\n\tname, err := unix.GetsockoptString(fd, sysProtoControl, uTunOptIfName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tiface, err := net.InterfaceByName(name)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &device{\n\t\tfile:           os.NewFile(uintptr(fd), \"\"),\n\t\tctx:            ctx,\n\t\tname:           name,\n\t\tinterfaceIndex: uint32(iface.Index),\n\t\tEndpoint:       channel.New(defaultDevOutQueueLen, uint32(iface.MTU), \"\"),\n\t}, nil\n}\n\n// Close closes both the tun-device and the Endpoint. This function overrides the LinkEndpoint.Close so\n// it can not return an error.\nfunc (d *device) Close() {\n\td.Endpoint.Close()\n\t_ = d.file.Close()\n}\n\nfunc (d *device) addSubnet(_ context.Context, subnet netip.Prefix) error {\n\tto := subnet.Addr().AsSlice()\n\tto[len(to)-1] = 1\n\tdest, _ := netip.AddrFromSlice(to)\n\tif err := d.setAddr(subnet, dest); err != nil {\n\t\treturn err\n\t}\n\treturn routing.Add(1, subnet, dest)\n}\n\nfunc (d *device) removeSubnet(_ context.Context, subnet netip.Prefix) error {\n\tto := subnet.Addr().AsSlice()\n\tto[len(to)-1] = 1\n\tdest, _ := netip.AddrFromSlice(to)\n\tif err := d.removeAddr(subnet, dest); err != nil {\n\t\treturn err\n\t}\n\treturn routing.Clear(1, subnet, dest)\n}\n\nfunc (d *device) readPacket(buf []byte) (int, error) {\n\treturn d.file.Read(buf)\n}\n\nconst prefixLen = 4\n\nfunc (d *device) headerSkip() int {\n\treturn prefixLen\n}\n\nfunc (d *device) writePacket(from *stack.PacketBuffer) (err error) {\n\tss := from.AsSlices()\n\tvar first []byte\n\tfor _, s := range ss {\n\t\tif len(s) > 0 {\n\t\t\tfirst = s\n\t\t\tbreak\n\t\t}\n\t}\n\tif first == nil {\n\t\treturn nil\n\t}\n\tipVer := first[0] >> 4\n\tvar af byte\n\tswitch ipVer {\n\tcase ipv4.Version:\n\t\taf = unix.AF_INET\n\tcase ipv6.Version:\n\t\taf = unix.AF_INET6\n\tdefault:\n\t\treturn errors.New(\"unable to determine IP version from packet\")\n\t}\n\twb := &d.wb\n\twb.Reset()\n\twb.WriteByte(0)\n\twb.WriteByte(0)\n\twb.WriteByte(0)\n\twb.WriteByte(af)\n\twb.Write(first)\n\tfor i := 1; i < len(ss); i++ {\n\t\twb.Write(ss[i])\n\t}\n\t_, err = d.file.Write(wb.Bytes())\n\treturn err\n}\n\n// Address structure for the SIOCAIFADDR ioctlHandle request\n//\n// See https://www.unix.com/man-page/osx/4/netintro/\ntype addrIfReq struct {\n\tname [unix.IFNAMSIZ]byte\n\taddr unix.RawSockaddrInet4\n\tdest unix.RawSockaddrInet4\n\tmask unix.RawSockaddrInet4\n}\n\n// Address structure for the SIOCAIFADDR_IN6 ioctlHandle request\n//\n// See https://www.unix.com/man-page/osx/4/netintro/\n\ntype addrLifetime struct {\n\texpire         float64 //nolint:unused //not used\n\tpreferred      float64 //nolint:unused // not used\n\tvalidLifeTime  uint32\n\tprefixLifeTime uint32\n}\n\ntype addrIfReq6 struct {\n\tname         [unix.IFNAMSIZ]byte\n\taddr         unix.RawSockaddrInet6\n\tdest         unix.RawSockaddrInet6\n\tmask         unix.RawSockaddrInet6\n\tflags        int32 //nolint:structcheck // this is the type returned by the kernel, not our own type\n\taddrLifetime addrLifetime\n}\n\n// SIOCAIFADDR_IN6 is the same ioctlHandle identifier as unix.SIOCAIFADDR adjusted with size of addrIfReq6.\nconst (\n\tSIOCAIFADDR_IN6       = (unix.SIOCAIFADDR & 0xe000ffff) | (uint(unsafe.Sizeof(addrIfReq6{})) << 16)\n\tND6_INFINITE_LIFETIME = 0xffffffff\n\tIN6_IFF_NODAD         = 0x0020\n\tIN6_IFF_SECURED       = 0x0400\n)\n\n// SIOCDIFADDR_IN6 is the same ioctlHandle identifier as unix.SIOCDIFADDR adjusted with size of addrIfReq6.\nconst SIOCDIFADDR_IN6 = (unix.SIOCDIFADDR & 0xe000ffff) | (uint(unsafe.Sizeof(addrIfReq6{})) << 16)\n\nfunc (d *device) setAddr(subnet netip.Prefix, to netip.Addr) error {\n\tif to.Is4() && subnet.Addr().Is4() {\n\t\treturn withSocket(unix.AF_INET, func(fd int) error {\n\t\t\tifreq := &addrIfReq{\n\t\t\t\taddr: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet4, Family: unix.AF_INET},\n\t\t\t\tdest: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet4, Family: unix.AF_INET},\n\t\t\t\tmask: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet4, Family: unix.AF_INET},\n\t\t\t}\n\t\t\tcopy(ifreq.name[:], d.name)\n\t\t\tcopy(ifreq.mask.Addr[:], net.CIDRMask(subnet.Bits(), 32))\n\t\t\tifreq.addr.Addr = subnet.Addr().As4()\n\t\t\tifreq.dest.Addr = to.As4()\n\t\t\terr := ioctl(fd, unix.SIOCAIFADDR, unsafe.Pointer(ifreq))\n\t\t\truntime.KeepAlive(ifreq)\n\t\t\treturn err\n\t\t})\n\t} else {\n\t\treturn withSocket(unix.AF_INET6, func(fd int) error {\n\t\t\tifreq := &addrIfReq6{\n\t\t\t\taddr:  unix.RawSockaddrInet6{Len: unix.SizeofSockaddrInet6, Family: unix.AF_INET6},\n\t\t\t\tmask:  unix.RawSockaddrInet6{Len: unix.SizeofSockaddrInet6, Family: unix.AF_INET6},\n\t\t\t\tflags: IN6_IFF_NODAD | IN6_IFF_SECURED,\n\t\t\t}\n\t\t\tifreq.addrLifetime.validLifeTime = ND6_INFINITE_LIFETIME\n\t\t\tifreq.addrLifetime.prefixLifeTime = ND6_INFINITE_LIFETIME\n\n\t\t\tcopy(ifreq.name[:], d.name)\n\t\t\tcopy(ifreq.mask.Addr[:], net.CIDRMask(subnet.Bits(), 128))\n\t\t\tifreq.addr.Addr = subnet.Addr().As16()\n\t\t\terr := ioctl(fd, SIOCAIFADDR_IN6, unsafe.Pointer(ifreq))\n\t\t\truntime.KeepAlive(ifreq)\n\t\t\treturn err\n\t\t})\n\t}\n}\n\nfunc (d *device) removeAddr(subnet netip.Prefix, to netip.Addr) error {\n\tif to.Is4() && subnet.Addr().Is4() {\n\t\treturn withSocket(unix.AF_INET, func(fd int) error {\n\t\t\tifreq := &addrIfReq{\n\t\t\t\taddr: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet6, Family: unix.AF_INET},\n\t\t\t\tdest: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet6, Family: unix.AF_INET},\n\t\t\t\tmask: unix.RawSockaddrInet4{Len: unix.SizeofSockaddrInet6, Family: unix.AF_INET},\n\t\t\t}\n\t\t\tcopy(ifreq.name[:], d.name)\n\t\t\tcopy(ifreq.mask.Addr[:], net.CIDRMask(subnet.Bits(), 32))\n\t\t\tifreq.addr.Addr = subnet.Addr().As4()\n\t\t\tifreq.dest.Addr = to.As4()\n\t\t\terr := ioctl(fd, unix.SIOCDIFADDR, unsafe.Pointer(ifreq))\n\t\t\truntime.KeepAlive(ifreq)\n\t\t\treturn err\n\t\t})\n\t} else {\n\t\treturn withSocket(unix.AF_INET6, func(fd int) error {\n\t\t\tifreq := &addrIfReq6{\n\t\t\t\taddr: unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},\n\t\t\t\tdest: unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},\n\t\t\t\tmask: unix.RawSockaddrInet6{Len: 28, Family: unix.AF_INET6},\n\t\t\t}\n\t\t\tifreq.addrLifetime.validLifeTime = ND6_INFINITE_LIFETIME\n\t\t\tifreq.addrLifetime.prefixLifeTime = ND6_INFINITE_LIFETIME\n\n\t\t\tcopy(ifreq.name[:], d.name)\n\t\t\tcopy(ifreq.mask.Addr[:], net.CIDRMask(subnet.Bits(), 128))\n\t\t\tifreq.addr.Addr = subnet.Addr().As16()\n\t\t\terr := ioctl(fd, SIOCDIFADDR_IN6, unsafe.Pointer(ifreq))\n\t\t\truntime.KeepAlive(ifreq)\n\t\t\treturn err\n\t\t})\n\t}\n}\n\nfunc withSocket(domain int, f func(fd int) error) error {\n\tfd, err := unix.Socket(domain, unix.SOCK_DGRAM, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer unix.Close(fd)\n\treturn f(fd)\n}\n\nfunc ioctl(socket int, request uint, requestData unsafe.Pointer) error {\n\treturn unix.IoctlSetInt(socket, request, int(uintptr(requestData)))\n}\n"
  },
  {
    "path": "pkg/vif/device_linux.go",
    "content": "package vif\n\nimport (\n\t\"context\"\n\tcryptoRand \"crypto/rand\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"unsafe\"\n\n\t\"github.com/vishvananda/netlink\"\n\t\"golang.org/x/sys/unix\"\n\t\"gvisor.dev/gvisor/pkg/rawfile\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/link/fdbased\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/subnet\"\n)\n\nconst devicePath = \"/dev/net/tun\"\n\ntype device struct {\n\tfd             int\n\tname           string\n\tendPoint       stack.LinkEndpoint\n\tinterfaceIndex uint32\n\tisTAP          bool\n}\n\nfunc RandomMAC() net.HardwareAddr {\n\taddr := make([]byte, 6)\n\t_, _ = cryptoRand.Read(addr)\n\t// Clear multicast\n\taddr[0] &^= 1\n\t// Set the local bit\n\taddr[0] |= 2\n\treturn addr\n}\n\nfunc openTun(ctx context.Context) (*device, error) {\n\t// https://www.kernel.org/doc/html/latest/networking/tuntap.html\n\n\tfd, err := unix.Open(devicePath, unix.O_RDWR, 0)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to open TUN device %s: %w\", devicePath, err)\n\t}\n\tunix.CloseOnExec(fd)\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t_ = unix.Close(fd)\n\t\t}\n\t}()\n\n\tifr, _ := unix.NewIfreq(\"tel%d\")\n\tuseTAP := client.GetConfig(ctx).Routing().UseTAP\n\tif useTAP {\n\t\tifr.SetUint16(unix.IFF_TAP | unix.IFF_NO_PI)\n\t} else {\n\t\tifr.SetUint16(unix.IFF_TUN | unix.IFF_NO_PI)\n\t}\n\n\terr = unix.IoctlSetInt(fd, unix.TUNSETIFF, int(uintptr(unsafe.Pointer(ifr))))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to set TUN device flags: %w\", err)\n\t}\n\n\t// Retrieve the name that was generated based on the \"tel%d\" template.\n\tname := ifr.Name()\n\n\t// Set non-blocking so that ReadPacket() doesn't hang for several seconds when the\n\t// fd is Closed. ReadPacket() will still wait for data to arrive.\n\t//\n\t// See: https://github.com/golang/go/issues/30426#issuecomment-470044803\n\terr = unix.SetNonblock(fd, true)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to set TAP fd non-blocking: %w\", err)\n\t}\n\tlink, err := netlink.LinkByName(name)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to retrieve TAP link: %w\", err)\n\t}\n\terr = netlink.LinkSetUp(link)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to set link UP: %w\", err)\n\t}\n\tattrs := link.Attrs()\n\treturn &device{fd: fd, name: name, interfaceIndex: uint32(attrs.Index), isTAP: useTAP}, nil\n}\n\nfunc (d *device) addSubnet(_ context.Context, pfx netip.Prefix) error {\n\tlink, err := netlink.LinkByIndex(int(d.interfaceIndex))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to find link for interface %s: %w\", d.name, err)\n\t}\n\taddr := &netlink.Addr{IPNet: subnet.PrefixToIPNet(pfx)}\n\tif err := netlink.AddrAdd(link, addr); err != nil {\n\t\treturn fmt.Errorf(\"failed to add address %s to interface %s: %w\", pfx, d.name, err)\n\t}\n\treturn nil\n}\n\nfunc (d *device) removeSubnet(_ context.Context, pfx netip.Prefix) error {\n\tlink, err := netlink.LinkByIndex(int(d.interfaceIndex))\n\tif err != nil {\n\t\treturn err\n\t}\n\taddr := &netlink.Addr{IPNet: subnet.PrefixToIPNet(pfx)}\n\treturn netlink.AddrDel(link, addr)\n}\n\nfunc (d *device) getMTU() (mtu uint32, err error) {\n\treturn rawfile.GetMTU(d.name)\n}\n\nfunc (d *device) createLinkEndpoint() (stack.LinkEndpoint, error) {\n\tmtu, err := d.getMTU()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\topts := &fdbased.Options{\n\t\tFDs:                []int{d.fd},\n\t\tMTU:                mtu,\n\t\tPacketDispatchMode: fdbased.RecvMMsg,\n\t}\n\tif d.isTAP {\n\t\tmac := RandomMAC()\n\t\topts.EthernetHeader = true\n\t\topts.Address = tcpip.LinkAddress(mac)\n\t}\n\tep, err := fdbased.New(opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\td.endPoint = ep\n\treturn ep, nil\n}\n\nfunc (d *device) Close() {\n\td.endPoint.Close()\n\t_ = unix.Close(d.fd)\n}\n\nfunc (d *device) WaitForDevice() {\n}\n"
  },
  {
    "path": "pkg/vif/device_notlinux.go",
    "content": "//go:build !linux\n\npackage vif\n\nimport (\n\t\"context\"\n\n\t\"gvisor.dev/gvisor/pkg/buffer\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/header\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\n// Queue length for outbound packets, arriving at fd side for read. Overflow\n// causes packet drops. gVisor implementation-specific.\nconst defaultDevOutQueueLen = 1024\n\n// This can be fairly large. We're only allocating one such buffer, and the\n// reads are non-blocking.\nconst ioBufferSize = 1 << 22\n\nfunc (d *device) createLinkEndpoint() (stack.LinkEndpoint, error) {\n\treturn d, nil\n}\n\nfunc (d *device) WaitForDevice() {\n\td.wg.Wait()\n}\n\nfunc (d *device) Attach(dp stack.NetworkDispatcher) {\n\tgo func() {\n\t\td.Endpoint.Attach(dp)\n\t\tif dp == nil {\n\t\t\t// Stack is closing\n\t\t\treturn\n\t\t}\n\t\tclog.Info(d.ctx, \"Starting Endpoint\")\n\t\tctx, cancel := context.WithCancel(d.ctx)\n\t\td.wg.Add(2)\n\t\tgo d.tunToDispatch(cancel)\n\t\td.dispatchToTun(ctx)\n\t}()\n}\n\nfunc (d *device) tunToDispatch(cancel context.CancelFunc) {\n\tdefer func() {\n\t\tcancel()\n\t\td.wg.Done()\n\t}()\n\tbuf := make([]byte, ioBufferSize)\n\tskip := d.headerSkip()\n\tfor {\n\t\tclog.Trace(d.ctx, \"readPacket\")\n\t\tn, err := d.readPacket(buf)\n\t\tif err != nil {\n\t\t\tif d.IsAttached() && d.ctx.Err() == nil {\n\t\t\t\tclog.Errorf(d.ctx, \"read packet error: %v\", err)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif n-skip <= 0 {\n\t\t\tcontinue\n\t\t}\n\t\tdata := buf[skip:n]\n\n\t\tvar ipv tcpip.NetworkProtocolNumber\n\t\tswitch header.IPVersion(data) {\n\t\tcase header.IPv4Version:\n\t\t\tipv = header.IPv4ProtocolNumber\n\t\tcase header.IPv6Version:\n\t\t\tipv = header.IPv6ProtocolNumber\n\t\tdefault:\n\t\t\tcontinue\n\t\t}\n\n\t\tpb := stack.NewPacketBuffer(stack.PacketBufferOptions{\n\t\t\tPayload: buffer.MakeWithData(data),\n\t\t})\n\n\t\td.InjectInbound(ipv, pb)\n\t\tpb.DecRef()\n\t}\n}\n\nfunc (d *device) dispatchToTun(ctx context.Context) {\n\tdefer d.wg.Done()\n\tfor {\n\t\tclog.Trace(d.ctx, \"ReadContext\")\n\t\tpb := d.ReadContext(ctx)\n\t\tif pb == nil {\n\t\t\tbreak\n\t\t}\n\t\tif err := d.writePacket(pb); err != nil {\n\t\t\tclog.Errorf(ctx, \"WritePacket failed: %v\", err)\n\t\t}\n\t\tpb.DecRef()\n\t}\n}\n"
  },
  {
    "path": "pkg/vif/device_unix.go",
    "content": "//go:build !windows\n\npackage vif\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n)\n\nfunc (d *device) setDNS(context.Context, string, netip.AddrPort, []string) (err error) {\n\t// DNS is configured by other means than through the actual device\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/vif/device_windows.go",
    "content": "package vif\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/netip\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"golang.org/x/sys/windows\"\n\t\"golang.zx2c4.com/wireguard/tun\"\n\t\"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/link/channel\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\n// This device will require that wintun.dll is available to the loader.\n// See: https://www.wintun.net/ for more info.\ntype device struct {\n\t*channel.Endpoint\n\tdev            tun.Device\n\tctx            context.Context\n\tname           string\n\tdns            netip.AddrPort\n\tinterfaceIndex uint32\n\tluid           winipcfg.LUID\n\twb             bytes.Buffer\n\twg             sync.WaitGroup\n}\n\nfunc openTun(ctx context.Context) (td *device, err error) {\n\tinterfaceFmt := \"tel%d\"\n\tifaceNumber := 0\n\tifaces, err := net.Interfaces()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get interfaces: %w\", err)\n\t}\n\tfor _, iface := range ifaces {\n\t\tclog.Tracef(ctx, \"Found interface %s\", iface.Name)\n\t\t// Parse the tel%d number if it's there\n\t\tvar num int\n\t\tif _, err := fmt.Sscanf(iface.Name, interfaceFmt, &num); err == nil {\n\t\t\tif num >= ifaceNumber {\n\t\t\t\tifaceNumber = num + 1\n\t\t\t}\n\t\t}\n\t}\n\tinterfaceName := fmt.Sprintf(interfaceFmt, ifaceNumber)\n\n\tclog.Infof(ctx, \"Creating interface %s\", interfaceName)\n\tdev, err := tun.CreateTUN(interfaceName, 0)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create TUN device: %w\", err)\n\t}\n\tname, err := dev.Name()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get real name of TUN device: %w\", err)\n\t}\n\tluid := winipcfg.LUID(dev.(*tun.NativeTun).LUID())\n\tiface, err := luid.Interface()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get interface for TUN device: %w\", err)\n\t}\n\n\tmtu, err := dev.MTU()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get MTU for TUN device: %w\", err)\n\t}\n\tif mtu < 1500 {\n\t\tmtu = 1500\n\t}\n\tclog.Debugf(ctx, \"using MTU = %d\", mtu)\n\treturn &device{\n\t\tEndpoint:       channel.New(defaultDevOutQueueLen, uint32(mtu), \"\"),\n\t\tdev:            dev,\n\t\tctx:            ctx,\n\t\tname:           name,\n\t\tinterfaceIndex: iface.InterfaceIndex,\n\t\tluid:           luid,\n\t}, nil\n}\n\n// Close closes both the Device and the Endpoint. This function overrides the LinkEndpoint.Close so\n// it cannot return an error.\nfunc (d *device) Close() {\n\td.Endpoint.Close()\n\n\t// The tun.NativeTun device has a closing mutex which is read locked during\n\t// a call to Read(). The read lock prevents a call to Close() to proceed\n\t// until Read() actually receives something. To resolve that \"deadlock\",\n\t// we call Close() in one goroutine to wait for the lock and write a bogus\n\t// message in another that will be returned by Read().\n\tcloseCh := make(chan error)\n\tgo func() {\n\t\t// first message is just to indicate that this goroutine has started\n\t\tcloseCh <- nil\n\t\tcloseCh <- d.dev.Close()\n\t\tclose(closeCh)\n\t}()\n\n\t// Not 100%, but we can be fairly sure that Close() is\n\t// hanging on the lock, or at least will be by the time\n\t// the Read() returns\n\t<-closeCh\n\n\t// Send something to the TUN device so that the Read\n\t// unlocks the NativeTun.closing mutex and let the actual\n\t// Close call continue\n\tconn, err := net.Dial(\"udp\", net.JoinHostPort(d.dns.String(), \"53\"))\n\tif err == nil {\n\t\t_, _ = conn.Write([]byte(\"bogus\"))\n\t}\n\t<-closeCh\n}\n\nfunc (d *device) getLUID() winipcfg.LUID {\n\treturn winipcfg.LUID(d.dev.(*tun.NativeTun).LUID())\n}\n\nfunc (d *device) addSubnet(_ context.Context, subnet netip.Prefix) error {\n\treturn d.getLUID().AddIPAddress(subnet)\n}\n\nfunc (d *device) removeSubnet(_ context.Context, subnet netip.Prefix) error {\n\treturn d.getLUID().DeleteIPAddress(subnet)\n}\n\nfunc (d *device) setDNS(ctx context.Context, clusterDomain string, server netip.AddrPort, searchList []string) (err error) {\n\t// This function must not be interrupted by a context cancellation, so we give it a timeout instead.\n\tclog.Debugf(ctx, \"SetDNS server: %s, searchList: %v, domain: %q\", server, searchList, clusterDomain)\n\tdefer clog.Debug(ctx, \"SetDNS done\")\n\n\tluid := d.getLUID()\n\tfamily := addressFamily(server.Addr())\n\tif d.dns.IsValid() {\n\t\tif oldFamily := addressFamily(d.dns.Addr()); oldFamily != family {\n\t\t\t_ = luid.FlushDNS(oldFamily)\n\t\t}\n\t}\n\td.dns = server\n\tclusterDomain = strings.TrimSuffix(clusterDomain, \".\")\n\tcdi := slices.Index(searchList, clusterDomain)\n\tswitch cdi {\n\tcase 0:\n\t\t// clusterDomain is already in first position\n\tcase -1:\n\t\t// clusterDomain is not included in the list\n\t\tsearchList = slices.Insert(searchList, 0, clusterDomain)\n\tdefault:\n\t\t// put clusterDomain first in list, but retain the order of remaining elements\n\t\tsearchList = slices.Insert(slices.Delete(searchList, cdi, cdi+1), 0, clusterDomain)\n\t}\n\treturn luid.SetDNS(family, []netip.Addr{d.dns.Addr()}, searchList)\n}\n\nfunc addressFamily(ip netip.Addr) winipcfg.AddressFamily {\n\tf := winipcfg.AddressFamily(windows.AF_INET6)\n\tif ip.Is4() {\n\t\tf = windows.AF_INET\n\t}\n\treturn f\n}\n\nfunc (d *device) headerSkip() int {\n\treturn 0\n}\n\nfunc (d *device) readPacket(buf []byte) (int, error) {\n\tsz := make([]int, 1)\n\tpacketsN, err := d.dev.Read([][]byte{buf}, sz, 0)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tif packetsN == 0 {\n\t\treturn 0, io.EOF\n\t}\n\treturn sz[0], nil\n}\n\nfunc (d *device) writePacket(from *stack.PacketBuffer) error {\n\twb := &d.wb\n\twb.Reset()\n\tfor _, s := range from.AsSlices() {\n\t\twb.Write(s)\n\t}\n\tpacketsN, err := d.dev.Write([][]byte{wb.Bytes()}, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif packetsN == 0 {\n\t\treturn io.EOF\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/vif/logging.go",
    "content": "package vif\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"time\"\n\n\t\"gvisor.dev/gvisor/pkg/log\"\n\n\t\"github.com/telepresenceio/clog\"\n)\n\ntype clogEmitter struct {\n\tcontext.Context\n}\n\nfunc (l clogEmitter) Emit(_ int, level log.Level, _ time.Time, format string, v ...interface{}) { //nolint:goprintffuncname // not our API\n\tswitch level {\n\tcase log.Debug:\n\t\tclog.Debugf(l, format, v...)\n\tcase log.Info:\n\t\tclog.Infof(l, format, v...)\n\tcase log.Warning:\n\t\tclog.Warnf(l, format, v...)\n\t}\n}\n\nfunc InitLogger(ctx context.Context) {\n\tlog.SetTarget(&clogEmitter{Context: ctx})\n\tvar gl log.Level\n\tswitch {\n\tcase clog.Enabled(ctx, slog.LevelDebug):\n\t\tgl = log.Debug\n\tcase clog.Enabled(ctx, slog.LevelInfo):\n\t\tgl = log.Info\n\tdefault:\n\t\tgl = log.Warning\n\t}\n\tlog.SetLevel(gl)\n}\n"
  },
  {
    "path": "pkg/vif/router.go",
    "content": "package vif\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/netip\"\n\t\"runtime\"\n\t\"slices\"\n\t\"sync\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/errcat\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/routing\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/subnet\"\n)\n\ntype Router struct {\n\tsync.RWMutex\n\t// The vif device that packets will be routed through\n\tdevice Device\n\t// The routing table that will be used to route packets\n\troutingTable routing.Table\n\t// A list of never proxied routes that have already been added to routing table\n\tstaticOverrides []routing.Route\n\t// The subnets that are currently being routed\n\troutedSubnets []netip.Prefix\n\t// The subnets that are allowed to be routed even in the presence of conflicting routes\n\twhitelistedSubnets []netip.Prefix\n}\n\nfunc NewRouter(device Device, table routing.Table) *Router {\n\treturn &Router{device: device, routingTable: table}\n}\n\nfunc (rt *Router) GetRoutedSubnets() []netip.Prefix {\n\trt.RLock()\n\trsn := slices.Clone(rt.routedSubnets)\n\trt.RUnlock()\n\treturn rsn\n}\n\nfunc (rt *Router) UpdateWhitelist(whitelist []netip.Prefix) {\n\trt.Lock()\n\trt.whitelistedSubnets = whitelist\n\trt.Unlock()\n}\n\nfunc (rt *Router) ValidateRoutes(ctx context.Context, routes []netip.Prefix) error {\n\t// We need the entire table because we need to check for any overlaps, not just \"is this IP already routed\"\n\ttable, err := routing.GetRoutingTable(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trt.RLock()\n\tnonWhitelisted := slices.DeleteFunc(slices.Clone(routes), func(r netip.Prefix) bool {\n\t\tfor _, w := range rt.whitelistedSubnets {\n\t\t\tif subnet.Covers(w, r) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\tfor _, er := range table {\n\t\t\tif r == er.RoutedNet && er.InterfaceName == rt.device.Name() {\n\t\t\t\t// Route is already in the routing table.\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\trt.RUnlock()\n\n\t// Slightly awkward nested loops, since they can both continue (i.e., there are probably wasted iterations), but it's\n\t// okay, there's not going to be hundreds of routes.\n\t// In any case, we really wanna run over the table as the outer loop, since it's bigger.\n\tfor _, tr := range table {\n\t\tclog.Tracef(ctx, \"checking for overlap with route %q\", tr)\n\t\tif (tr.RoutedNet.Bits() == 0 || tr.Default) || // Default route, overlapped if needed\n\t\t\tsubnet.IsHalfOfDefault(tr.RoutedNet) || // OpenVPN covers half the address space with a /1 route and the other half with another. This is its way of doing a default route.\n\t\t\ttr.InterfaceName == rt.device.Name() { // This is the interface we're routing through, so we can overlap it\n\t\t\tcontinue\n\t\t}\n\t\tfor _, r := range nonWhitelisted {\n\t\t\tif tr.RoutedNet.Overlaps(r) {\n\t\t\t\treturn errcat.Config.New(fmt.Sprintf(\n\t\t\t\t\t\"subnet %s overlaps with existing route %q. Please see %s for more information\",\n\t\t\t\t\tr, tr, \"https://www.telepresence.io/docs/reference/vpn\",\n\t\t\t\t))\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (rt *Router) Routes(addr netip.Addr) bool {\n\trt.RLock()\n\thasRoute := false\n\tfor _, sn := range rt.routedSubnets {\n\t\tif sn.Contains(addr) {\n\t\t\thasRoute = true\n\t\t\tbreak\n\t\t}\n\t}\n\tif hasRoute {\n\t\tfor i := range rt.staticOverrides {\n\t\t\trn := &rt.staticOverrides[i]\n\t\t\tif rn.RoutedNet.Contains(addr) && uint32(rn.InterfaceIndex) != rt.device.Index() {\n\t\t\t\thasRoute = false\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\trt.RUnlock()\n\treturn hasRoute\n}\n\nfunc (rt *Router) UpdateRoutes(ctx context.Context, pleaseProxy, dontProxy, dontProxyOverrides []netip.Prefix) error {\n\trt.Lock()\n\tdefer rt.Unlock()\n\n\t// Remove all current static routes so that they don't affect the routes for subnets\n\t// that we're about to add.\n\trt.dropStaticOverrides(ctx)\n\n\t// Remove all no longer desired subnets from the routedSubnets\n\tvar removed []netip.Prefix\n\trt.routedSubnets, removed = subnet.Partition(rt.routedSubnets, func(_ int, sn netip.Prefix) bool {\n\t\tfor _, d := range pleaseProxy {\n\t\t\tif sn == d {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\n\t// Remove already routed subnets from the pleaseProxy list\n\tadded := slices.DeleteFunc(pleaseProxy, func(sn netip.Prefix) bool {\n\t\tfor _, d := range rt.routedSubnets {\n\t\t\tif sn == d {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\n\t// Add pleaseProxy subnets to the currently routed subnets\n\trt.routedSubnets = append(rt.routedSubnets, added...)\n\n\tfor _, sn := range removed {\n\t\tif err := rt.device.RemoveSubnet(ctx, sn); err != nil {\n\t\t\tclog.Errorf(ctx, \"failed to remove subnet %s: %v\", sn, err)\n\t\t}\n\t}\n\n\tourIdx := int(rt.device.Index())\n\tourName := rt.device.Name()\n\n\tvar staticRoutes []routing.Route\n\tconst linux = runtime.GOOS == \"linux\"\n\tfor _, sn := range added {\n\t\tif linux && sn.IsSingleIP() {\n\t\t\tstaticRoutes = append(staticRoutes, routing.NewRoute(sn, ourIdx, ourName))\n\t\t\tcontinue\n\t\t}\n\n\t\t// On linux, this adds a link, so it's still relevant after adding a static route.\n\t\tif err := rt.device.AddSubnet(ctx, sn); err != nil {\n\t\t\tclog.Errorf(ctx, \"failed to add subnet %s: %v\", sn, err)\n\t\t\tcontinue\n\t\t}\n\n\t\tif linux {\n\t\t\t// On linux, we use static routes for conflicting subnets, because those subnets will then belong\n\t\t\t// to our own routing table.\n\t\t\tif slices.ContainsFunc(rt.whitelistedSubnets, func(r netip.Prefix) bool { return r.Overlaps(sn) }) {\n\t\t\t\tclog.Debugf(ctx, \"Using static route for %s because it is an override\", sn)\n\t\t\t\tstaticRoutes = append(staticRoutes, routing.NewRoute(sn, ourIdx, ourName))\n\t\t\t}\n\t\t}\n\t}\n\tdr, err := routing.DefaultRoute(ctx)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// All subnets in neverProxy have been verified as being routed by the TUN-device, so we\n\t// route them to the default device.\n\tfor _, sn := range dontProxy {\n\t\tstaticRoutes = append(staticRoutes, routing.NewRoute(sn, dr.InterfaceIndex, dr.InterfaceName))\n\t}\n\n\t// ... except for the never proxy overrides, which will be routed to our device.\n\tfor _, sn := range dontProxyOverrides {\n\t\tstaticRoutes = append(staticRoutes, routing.NewRoute(sn, ourIdx, ourName))\n\t}\n\n\taddRts, removeRts := rt.createRoutesDelta(staticRoutes)\n\tfor i := range addRts {\n\t\tr := &addRts[i]\n\t\tif err = rt.routingTable.Add(ctx, r); err != nil {\n\t\t\tclog.Errorf(ctx, \"failed to add static route %s: %v\", r, err)\n\t\t}\n\t}\n\tfor i := range removeRts {\n\t\tr := &removeRts[i]\n\t\tif err = rt.routingTable.Remove(ctx, r); err != nil {\n\t\t\tclog.Errorf(ctx, \"failed to remove static route %s: %v\", r, err)\n\t\t}\n\t}\n\trt.staticOverrides = staticRoutes\n\treturn nil\n}\n\nfunc (rt *Router) createRoutesDelta(rs []routing.Route) (added, removed []routing.Route) {\n\tfor _, r := range rs {\n\t\tif !slices.Contains(rt.staticOverrides, r) {\n\t\t\tadded = append(added, r)\n\t\t}\n\t}\n\tfor _, r := range rt.staticOverrides {\n\t\tif !slices.Contains(rs, r) {\n\t\t\tremoved = append(removed, r)\n\t\t}\n\t}\n\treturn added, removed\n}\n\nfunc (rt *Router) dropStaticOverrides(ctx context.Context) {\n\t// Remove all current static routes so that they don't affect the routes for subnets\n\t// that we're about to add.\n\tfor i := range rt.staticOverrides {\n\t\tr := &rt.staticOverrides[i]\n\t\tif err := rt.routingTable.Remove(ctx, r); err != nil {\n\t\t\tclog.Errorf(ctx, \"failed to remove static route %s: %v\", r, err)\n\t\t}\n\t}\n\trt.staticOverrides = nil\n}\n\nfunc (rt *Router) Close(ctx context.Context) {\n\trt.RLock()\n\tfor _, sn := range rt.routedSubnets {\n\t\tif err := rt.device.RemoveSubnet(ctx, sn); err != nil {\n\t\t\tclog.Errorf(ctx, \"failed to remove subnet %s: %v\", sn, err)\n\t\t}\n\t}\n\trt.dropStaticOverrides(ctx)\n\trt.RUnlock()\n}\n\nfunc (rt *Router) Table() routing.Table {\n\treturn rt.routingTable\n}\n"
  },
  {
    "path": "pkg/vif/router_test.go",
    "content": "package vif_test\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"os/exec\"\n\t\"regexp\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/suite\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/dos\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/log\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/routing\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/subnet\"\n)\n\ntype RoutingSuite struct {\n\tsuite.Suite\n}\n\nfunc TestRouting(t *testing.T) {\n\tsuite.Run(t, new(RoutingSuite))\n}\n\nfunc getCidr(byte3, byte4 byte, mask int) netip.Prefix {\n\t// 198.18.0.0/15 is reserved for benchmarking.\n\tip := netip.AddrFrom4([4]byte{198, 18, byte3, byte4})\n\treturn netip.PrefixFrom(ip, mask)\n}\n\nfunc (s *RoutingSuite) SetupSuite() {\n\t// Compile the router binary\n\tif runtime.GOOS == \"windows\" {\n\t\t// Run \"make wintun.dll\" in the ../../ directory\n\t\terr := exec.CommandContext(context.Background(), \"make\", \"-C\", \"../../\", \"build-output/bin/wintun.dll\").Run()\n\t\ts.Require().NoError(err)\n\t\t// That'll place the DLL in ../../build-output/bin/wintun.dll so copy it to testdata/router\n\t\terr = exec.CommandContext(context.Background(), \"cp\", \"../../build-output/bin/wintun.dll\", \"testdata/router/wintun.dll\").Run()\n\t\ts.Require().NoError(err)\n\t\terr = exec.CommandContext(context.Background(), \"go\", \"build\", \"-o\", \"testdata\\\\router\\\\router.exe\", \"testdata\\\\router\\\\main.go\").Run()\n\t\ts.Require().NoError(err)\n\t} else {\n\t\terr := exec.CommandContext(context.Background(), \"go\", \"build\", \"-o\", \"testdata/router/router\", \"testdata/router/main.go\").Run()\n\t\ts.Require().NoError(err)\n\t}\n\t// Make sure there's no existing route\n\tcidr := getCidr(2, 1, 32)\n\troute, err := routing.GetRoute(context.Background(), cidr)\n\ts.Require().NoError(err)\n\ts.Require().True(route.Default || subnet.IsHalfOfDefault(route.RoutedNet), \"There should be no route for %s, or everything will fail. Route is: %s\", cidr.Addr(), route)\n}\n\n// The routes are all gonna be inside 100.64.0.0/10 which is assigned as a reserved block for NAT. Github machines map 10/8 sometimes, so we wanna make sure not to conflict\n\nfunc (s *RoutingSuite) Test_RouteIsAdded() {\n\tctx := context.Background()\n\tcidr := getCidr(2, 0, 24)\n\n\tipnet := getCidr(2, 1, 32)\n\n\tdevice, routerCancel, err := s.runRouter(ctx, cidr.String())\n\ts.Require().NoError(err)\n\tdefer routerCancel()\n\n\troute, err := routing.GetRoute(ctx, ipnet)\n\ts.Require().NoError(err)\n\t// Ensure that the route is for the right device\n\ts.Require().Equal(device, route.InterfaceName)\n}\n\nfunc (s *RoutingSuite) Test_RouteIsRemoved() {\n\tctx := context.Background()\n\tcidr := getCidr(2, 0, 24)\n\tdevice, routerCancel, err := s.runRouter(ctx, cidr.String())\n\ts.Require().NoError(err)\n\n\trouterCancel()\n\n\tipnet := getCidr(2, 1, 32)\n\troute, err := routing.GetRoute(ctx, ipnet)\n\ts.Require().NoError(err)\n\n\ts.Require().NotEqual(device, route.InterfaceName)\n}\n\nfunc (s *RoutingSuite) Test_RouteIsBlackListed() {\n\tctx := context.Background()\n\tcidrYes := getCidr(2, 0, 24)\n\tcidrNo := getCidr(2, 4, 32)\n\toldRoute, err := routing.GetRoute(ctx, cidrNo)\n\ts.Require().NoError(err)\n\n\tdevice, routerCancel, err := s.runRouter(ctx, cidrYes.String(), \"!\"+cidrNo.String())\n\ts.Require().NoError(err)\n\tdefer routerCancel()\n\n\troute, err := routing.GetRoute(ctx, cidrNo)\n\ts.Require().NoError(err)\n\n\ts.Require().Equal(oldRoute.InterfaceName, route.InterfaceName, \"Expected route %s got %s\", oldRoute, route)\n\ts.Require().NotEqual(device, route.InterfaceName)\n}\n\nfunc (s *RoutingSuite) Test_RoutingTable() {\n\tctx := context.Background()\n\tcidr := getCidr(2, 0, 24)\n\n\tdevice, routerCancel, err := s.runRouter(ctx, cidr.String())\n\ts.Require().NoError(err)\n\tdefer routerCancel()\n\n\troutes, err := routing.GetRoutingTable(ctx)\n\ts.Require().NoError(err)\n\tdeviceFound := false\n\tcidrFound := false\n\tfor _, route := range routes {\n\t\tif route.InterfaceName == device {\n\t\t\tdeviceFound = true\n\t\t\ts.Require().False(route.Default, fmt.Sprintf(\"Route %s is default\", route.String()))\n\t\t\ts.Require().False(route.RoutedNet.Bits() == 0, fmt.Sprintf(\"Route %s has zero mask\", route.String()))\n\t\t\t// Linux and Windows will automatically add a bunch of multicast routes, which we can ignore as they're not actually for routing through the device.\n\t\t\tif !route.RoutedNet.Addr().IsMulticast() {\n\t\t\t\tif !route.RoutedNet.Addr().Is4() {\n\t\t\t\t\ts.Require().Contains([]int{128, 64}, route.RoutedNet.Bits(), fmt.Sprintf(\"Route %s is not a /128 or /64 mask\", route.String()))\n\t\t\t\t} else {\n\t\t\t\t\t// 255.255.255.255/32 is a special broadcast address that won't actually be used for routing\n\t\t\t\t\tif route.RoutedNet.Addr() != netip.AddrFrom4([4]byte{255, 255, 255, 255}) {\n\t\t\t\t\t\ts.Require().True(cidr.Contains(route.RoutedNet.Addr()), fmt.Sprintf(\"Route %s is not contained in %s\", route.String(), cidr))\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.Require().Equal(32, route.RoutedNet.Bits(), fmt.Sprintf(\"Route %s is not a /32 mask\", route.String()))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif route.RoutedNet.String() == cidr.String() {\n\t\t\t\tcidrFound = true\n\t\t\t}\n\t\t}\n\t}\n\ts.Require().True(deviceFound)\n\ts.Require().True(cidrFound)\n}\n\nfunc (s *RoutingSuite) Test_ConflictingRoutes() {\n\t// Start two routers with conflicting routes\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\tcidr1 := getCidr(2, 0, 26)\n\tcidr2 := getCidr(2, 32, 27)\n\n\t_, routerCancel1, err := s.runRouter(ctx, cidr1.String())\n\ts.Require().NoError(err)\n\tdefer routerCancel1()\n\n\t_, routerCancel2, err := s.runRouter(ctx, cidr2.String())\n\tif routerCancel2 != nil {\n\t\t// Make sure the second router doesn't leak\n\t\tdefer routerCancel2()\n\t}\n\ts.Require().Error(err)\n}\n\nfunc (s *RoutingSuite) Test_WhitelistedRoutes() {\n\tctx := context.Background()\n\toriginal := getCidr(0, 0, 16)\n\tconflicting := getCidr(4, 0, 24)\n\twhitelist := getCidr(0, 0, 21)\n\n\tdevice1, routerCancel, err := s.runRouter(ctx, original.String())\n\ts.Require().NoError(err)\n\tdefer routerCancel()\n\n\tdevice2, routerCancel2, err := s.runRouter(ctx, conflicting.String(), \"+\"+whitelist.String())\n\ts.Require().NoError(err)\n\tdefer routerCancel2()\n\n\ts.Require().NotEqual(device1, device2)\n\n\tipnet := getCidr(4, 2, 32)\n\n\troute, err := routing.GetRoute(ctx, ipnet)\n\ts.Require().NoError(err)\n\t// Ensure that the route is for the right device\n\ts.Require().Equal(device2, route.InterfaceName, \"Route %s is not for device %s\", route, device2)\n}\n\nfunc (s *RoutingSuite) Test_VPNConflicts() {\n\tctx := context.Background()\n\tcidr, ok := dos.LookupEnv(ctx, \"VPN_CIDR\")\n\tif !ok {\n\t\ts.T().Skip(\"VPN_CIDR not set, skipping test\")\n\t}\n\t_, ipnet, err := net.ParseCIDR(cidr)\n\ts.Require().NoError(err)\n\tones, _ := ipnet.Mask.Size()\n\ts.Require().LessOrEqual(ones, 28, \"VPN_CIDR mask is too small\")\n\tip := ipnet.IP.To4()\n\ts.Require().Equal(uint8(0x0), ip[3], \"VPN_CIDR must begin at 0\")\n\tip[3] = 8\n\tconflicting := fmt.Sprintf(\"%s/29\", ip.String())\n\n\t_, routerCancel, err := s.runRouter(ctx, conflicting)\n\tif err == nil {\n\t\tdefer routerCancel()\n\t}\n\ts.Require().Error(err)\n}\n\nfunc (s *RoutingSuite) Test_VPNConflictsWithWhitelist() {\n\tctx := context.Background()\n\tcidr, ok := dos.LookupEnv(ctx, \"VPN_CIDR\")\n\tif !ok {\n\t\ts.T().Skip(\"VPN_CIDR not set, skipping test\")\n\t}\n\tipnet, err := netip.ParsePrefix(cidr)\n\ts.Require().NoError(err)\n\tones := ipnet.Bits()\n\ts.Require().LessOrEqual(ones, 28, \"VPN_CIDR mask is too small\")\n\tip := ipnet.Addr().As4()\n\ts.Require().Equal(uint8(0x0), ip[3], \"VPN_CIDR must begin at 0\")\n\tip[3] = 8\n\tia := netip.AddrFrom4(ip)\n\tconflicting := fmt.Sprintf(\"%s/29\", ia.String())\n\n\tdevice, routerCancel, err := s.runRouter(ctx, conflicting, \"+\"+cidr)\n\ts.Require().NoError(err)\n\tdefer routerCancel()\n\n\tip[3] += 1\n\tia = netip.AddrFrom4(ip)\n\troute, err := routing.GetRoute(ctx, netip.PrefixFrom(ia, 32))\n\ts.Require().NoError(err)\n\ts.Require().Equal(device, route.InterfaceName)\n}\n\nfunc (s *RoutingSuite) Test_GetRoute() {\n\tctx := context.Background()\n\tcidr := getCidr(2, 0, 24)\n\n\tipnet := getCidr(2, 1, 32)\n\n\tdevice, routerCancel, err := s.runRouter(ctx, cidr.String())\n\ts.Require().NoError(err)\n\tdefer routerCancel()\n\n\troute, err := routing.GetRoute(ctx, ipnet)\n\n\t// We know what this route is supposed to look like cause we just added it. Make sure it matches.\n\ts.Require().NoError(err)\n\ts.Require().Equal(device, route.InterfaceName)\n\ts.Require().Equal(cidr, route.RoutedNet)\n\ts.Require().False(route.Default)\n\t// s.Require().NotNil(route.Gateway) there's no gateway when scope == link, and that's OK.\n\ts.Require().Equal(cidr.Addr(), route.LocalIP)\n}\n\nfunc (s *RoutingSuite) printRoutingTable(ctx context.Context) { //nolint:unused // Useful for debugging\n\tvar err error\n\t// Print out the routing table for debugging\n\tswitch runtime.GOOS {\n\tcase \"darwin\":\n\t\terr = exec.CommandContext(ctx, \"netstat\", \"-nr\").Run()\n\tcase \"linux\":\n\t\terr = exec.CommandContext(ctx, \"ip\", \"route\", \"show\", \"table\", \"all\").Run()\n\tcase \"windows\":\n\t\terr = exec.CommandContext(ctx, \"route\", \"print\").Run()\n\t}\n\ts.Require().NoError(err)\n\t// Print out the table rules for debugging\n\tswitch runtime.GOOS {\n\tcase \"darwin\":\n\t\terr = exec.CommandContext(ctx, \"netstat\", \"-nr\", \"-f\", \"inet\", \"-f\", \"inet6\").Run()\n\tcase \"linux\":\n\t\terr = exec.CommandContext(ctx, \"ip\", \"rule\", \"show\").Run()\n\tcase \"windows\":\n\t\terr = exec.CommandContext(ctx, \"netsh\", \"interface\", \"ipv4\", \"show\", \"route\").Run()\n\t}\n\ts.Require().NoError(err)\n}\n\nfunc (s *RoutingSuite) runRouter(pCtx context.Context, args ...string) (string, context.CancelFunc, error) {\n\tpc, _, _, ok := runtime.Caller(1)\n\ts.Require().True(ok)\n\tdetails := runtime.FuncForPC(pc)\n\tpCtx = clog.With(pCtx, \"test\", regexp.MustCompile(`^.*\\.(.*)$`).ReplaceAllString(details.Name(), \"$1\"))\n\n\toutRead, outWrite, err := os.Pipe()\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\tinRead, inWrite, err := os.Pipe()\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\n\tpCtx, pCancel := context.WithCancel(pCtx)\n\n\tvar cmd *exec.Cmd\n\tif runtime.GOOS == \"windows\" {\n\t\tcmd = exec.CommandContext(pCtx, \"testdata\\\\router\\\\router.exe\", args...)\n\t} else {\n\t\targs = append([]string{\"./testdata/router/router\"}, args...)\n\t\tcmd = exec.CommandContext(pCtx, \"sudo\", args...)\n\t}\n\n\tcmd.Stdout = outWrite\n\tcmd.Stdin = inRead\n\terr = cmd.Start()\n\tif err != nil {\n\t\tpCancel()\n\t\treturn \"\", nil, err\n\t}\n\tpCtx = clog.With(pCtx, \"pid\", cmd.Process.Pid)\n\n\twg := log.NewGroup(pCtx)\n\n\treadyCh := make(chan string)\n\tdefer close(readyCh)\n\terrCh := make(chan error)\n\tdoneCh := make(chan struct{})\n\tcmdCtx, cmdCancel := context.WithCancel(pCtx)\n\twg.Go(\"cmdCleanup\", func(ctx context.Context) error {\n\t\tdefer func() {\n\t\t\tinWrite.Close()\n\t\t\toutRead.Close()\n\t\t\toutWrite.Close()\n\t\t\tinRead.Close()\n\t\t}()\n\t\tselect {\n\t\tcase <-cmdCtx.Done():\n\t\tcase <-doneCh:\n\t\t}\n\n\t\t_, err := inWrite.WriteString(\"\\n\")\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\ttime.Sleep(500 * time.Millisecond)\n\t\treturn nil\n\t})\n\twg.Go(\"readStdout\", func(ctx context.Context) error {\n\t\tscanner := bufio.NewScanner(outRead)\n\t\tfor scanner.Scan() {\n\t\t\ttext := scanner.Text()\n\t\t\tif strings.HasPrefix(text, \"Device: \") {\n\t\t\t\treadyCh <- strings.TrimSpace(strings.TrimPrefix(text, \"Device: \"))\n\t\t\t}\n\t\t\tclog.Infof(ctx, \"router: %s\", text)\n\t\t}\n\t\tclog.Infof(ctx, \"router: EOF\")\n\t\treturn nil\n\t})\n\twg.Go(\"run\", func(ctx context.Context) error {\n\t\tdefer close(doneCh)\n\t\treturn cmd.Wait()\n\t})\n\tgo func() {\n\t\tdefer close(errCh)\n\t\terrCh <- wg.Wait()\n\t}()\n\n\tcanceler := func() {\n\t\tdefer pCancel()\n\t\tcmdCancel()\n\t\tselect {\n\t\tcase <-time.After(10 * time.Second):\n\t\t\ts.FailNow(\"Router did not exit in time\")\n\t\tcase err := <-errCh:\n\t\t\ts.Require().NoError(err)\n\t\t}\n\t}\n\n\tselect {\n\tcase device := <-readyCh:\n\t\treturn device, canceler, nil\n\tcase err := <-errCh:\n\t\tcanceler()\n\t\treturn \"\", nil, err\n\tcase <-pCtx.Done():\n\t\tcanceler()\n\t\treturn \"\", nil, pCtx.Err()\n\tcase <-time.After(45 * time.Second):\n\t\tcanceler()\n\t\treturn \"\", nil, fmt.Errorf(\"router did not start in time\")\n\t}\n}\n"
  },
  {
    "path": "pkg/vif/stack.go",
    "content": "package vif\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"time\"\n\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/header\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/arp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv4\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv6\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/icmp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/tcp\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/transport/udp\"\n\t\"gvisor.dev/gvisor/pkg/waiter\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/client\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/iputil\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/types\"\n)\n\nfunc NewStack(ctx context.Context, dev stack.LinkEndpoint, streamCreator tunnel.StreamCreator) (*stack.Stack, tcpip.NICID, error) {\n\tpfs := []stack.NetworkProtocolFactory{\n\t\tipv4.NewProtocol,\n\t\tipv6.NewProtocol,\n\t}\n\tif client.GetConfig(ctx).Routing().UseTAP {\n\t\tpfs = append(pfs, arp.NewProtocol)\n\t}\n\ts := stack.New(stack.Options{\n\t\tNetworkProtocols: pfs,\n\t\tTransportProtocols: []stack.TransportProtocolFactory{\n\t\t\ticmp.NewProtocol4,\n\t\t\ticmp.NewProtocol6,\n\t\t\ttcp.NewProtocol,\n\t\t\tudp.NewProtocol,\n\t\t},\n\t\tHandleLocal: false,\n\t})\n\tif err := setDefaultOptions(s); err != nil {\n\t\treturn nil, 0, err\n\t}\n\tnicID, err := setNIC(ctx, s, dev)\n\tif err != nil {\n\t\treturn nil, 0, err\n\t}\n\tsetTCPHandler(ctx, s, streamCreator)\n\tsetUDPHandler(ctx, s, streamCreator)\n\treturn s, nicID, nil\n}\n\n// maxInFlight specifies the max number of in-flight connection attempts.\nconst maxInFlight = 1024\n\n// keepAliveIdle is used as the very first keep-alive interval. Subsequent intervals\n// use keepAliveInterval.\nconst keepAliveIdle = 18 * time.Second\n\n// keepAliveInterval is the interval between sending keep-alive packets. We keep this fairly short\n// because we don't worry too much about draining batteries on cellphones.\nconst keepAliveInterval = 9 * time.Second\n\n// keepAliveCount is the max number of keep-alive probes that can be sent\n// before the connection is killed due to lack of response.\nconst keepAliveCount = 10\n\ntype idStringer stack.TransportEndpointID\n\nfunc (i idStringer) String() string {\n\treturn fmt.Sprintf(\"%s -> %s\",\n\t\tiputil.JoinIpPort(i.RemoteAddress.AsSlice(), i.RemotePort),\n\t\tiputil.JoinIpPort(i.LocalAddress.AsSlice(), i.LocalPort))\n}\n\nfunc setDefaultOptions(s *stack.Stack) error {\n\t// Forwarding\n\tif err := s.SetForwardingDefaultAndAllNICs(ipv4.ProtocolNumber, true); err != nil {\n\t\treturn fmt.Errorf(\"SetForwardingDefaultAndAllNICs(ipv4, %t): %s\", true, err)\n\t}\n\tif err := s.SetForwardingDefaultAndAllNICs(ipv6.ProtocolNumber, true); err != nil {\n\t\treturn fmt.Errorf(\"SetForwardingDefaultAndAllNICs(ipv6, %t): %s\", true, err)\n\t}\n\tttl := tcpip.DefaultTTLOption(64)\n\tif err := s.SetNetworkProtocolOption(ipv4.ProtocolNumber, &ttl); err != nil {\n\t\treturn fmt.Errorf(\"SetDefaultTTL(ipv4, %d): %s\", ttl, err)\n\t}\n\tif err := s.SetNetworkProtocolOption(ipv6.ProtocolNumber, &ttl); err != nil {\n\t\treturn fmt.Errorf(\"SetDefaultTTL(ipv6, %d): %s\", ttl, err)\n\t}\n\treturn nil\n}\n\nfunc setNIC(ctx context.Context, s *stack.Stack, ep stack.LinkEndpoint) (tcpip.NICID, error) {\n\tnicID := s.NextNICID()\n\tif err := s.CreateNICWithOptions(nicID, ep, stack.NICOptions{Name: \"tel\", Context: ctx}); err != nil {\n\t\treturn 0, fmt.Errorf(\"create NIC failed: %s\", err)\n\t}\n\tif err := s.SetPromiscuousMode(nicID, true); err != nil {\n\t\treturn 0, fmt.Errorf(\"SetPromiscuousMode(%d, %t): %s\", nicID, true, err)\n\t}\n\tif err := s.SetSpoofing(nicID, true); err != nil {\n\t\treturn 0, fmt.Errorf(\"SetSpoofing(%d, %t): %s\", nicID, true, err)\n\t}\n\tif err := s.SetNICAddress(nicID, ep.LinkAddress()); err != nil {\n\t\treturn 0, fmt.Errorf(\"SetNICAddress(%d, %s): %s\", nicID, ep.LinkAddress(), err)\n\t}\n\ts.SetRouteTable([]tcpip.Route{\n\t\t{\n\t\t\tDestination: header.IPv4EmptySubnet,\n\t\t\tNIC:         nicID,\n\t\t},\n\t\t{\n\t\t\tDestination: header.IPv6EmptySubnet,\n\t\t\tNIC:         nicID,\n\t\t},\n\t})\n\treturn nicID, nil\n}\n\nfunc forwardTCP(ctx context.Context, streamCreator tunnel.StreamCreator, fr *tcp.ForwarderRequest) {\n\tvar ep tcpip.Endpoint\n\tvar err tcpip.Error\n\tid := fr.ID()\n\tclog.Tracef(ctx, \"Forward TCP %s\", idStringer(id))\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tmsg := fmt.Sprintf(\"forward TCP %s: %s\", idStringer(id), err)\n\t\t\tclog.Error(ctx, msg)\n\t\t}\n\t}()\n\n\twq := waiter.Queue{}\n\tif ep, err = fr.CreateEndpoint(&wq); err != nil {\n\t\tfr.Complete(true)\n\t\treturn\n\t}\n\tdefer fr.Complete(false)\n\n\tso := ep.SocketOptions()\n\tso.SetKeepAlive(true)\n\n\tidle := tcpip.KeepaliveIdleOption(keepAliveIdle)\n\tif err = ep.SetSockOpt(&idle); err != nil {\n\t\treturn\n\t}\n\n\tivl := tcpip.KeepaliveIntervalOption(keepAliveInterval)\n\tif err = ep.SetSockOpt(&ivl); err != nil {\n\t\treturn\n\t}\n\n\tif err = ep.SetSockOptInt(tcpip.KeepaliveCountOption, keepAliveCount); err != nil {\n\t\treturn\n\t}\n\tdispatchToStream(ctx, newConnID(header.TCPProtocolNumber, id), gonet.NewTCPConn(&wq, ep), streamCreator)\n}\n\nfunc setTCPHandler(ctx context.Context, s *stack.Stack, streamCreator tunnel.StreamCreator) {\n\tif err := s.SetTransportProtocolOption(tcp.ProtocolNumber,\n\t\t&tcpip.TCPSendBufferSizeRangeOption{\n\t\t\tMin:     tcp.MinBufferSize,\n\t\t\tDefault: tcp.DefaultSendBufferSize,\n\t\t\tMax:     tcp.MaxBufferSize,\n\t\t}); err != nil {\n\t\treturn\n\t}\n\n\tif err := s.SetTransportProtocolOption(tcp.ProtocolNumber,\n\t\t&tcpip.TCPReceiveBufferSizeRangeOption{\n\t\t\tMin:     tcp.MinBufferSize,\n\t\t\tDefault: tcp.DefaultSendBufferSize,\n\t\t\tMax:     tcp.MaxBufferSize,\n\t\t}); err != nil {\n\t\treturn\n\t}\n\n\tsa := tcpip.TCPSACKEnabled(true)\n\ts.SetTransportProtocolOption(tcp.ProtocolNumber, &sa)\n\n\t// Enable Receive Buffer Auto-Tuning, see:\n\t// https://github.com/google/gvisor/issues/1666\n\tmo := tcpip.TCPModerateReceiveBufferOption(true)\n\ts.SetTransportProtocolOption(tcp.ProtocolNumber, &mo)\n\n\tf := tcp.NewForwarder(s, 0, maxInFlight, func(fr *tcp.ForwarderRequest) {\n\t\tforwardTCP(ctx, streamCreator, fr)\n\t})\n\ts.SetTransportProtocolHandler(tcp.ProtocolNumber, f.HandlePacket)\n}\n\nvar blockedUDPPorts = map[uint16]bool{ //nolint:gochecknoglobals // constant\n\t137: true, // NETBIOS Name Service\n\t138: true, // NETBIOS Datagram Service\n\t139: true, // NETBIOS\n}\n\nfunc forwardUDP(ctx context.Context, streamCreator tunnel.StreamCreator, fr *udp.ForwarderRequest) bool {\n\tid := fr.ID()\n\tif _, ok := blockedUDPPorts[id.LocalPort]; ok {\n\t\treturn false\n\t}\n\n\twq := waiter.Queue{}\n\tep, err := fr.CreateEndpoint(&wq)\n\tif err != nil {\n\t\tmsg := fmt.Sprintf(\"forward UDP %s: %s\", idStringer(id), err)\n\t\tclog.Error(ctx, msg)\n\t\treturn false\n\t}\n\treturn dispatchToStream(ctx, newConnID(udp.ProtocolNumber, id), gonet.NewUDPConn(&wq, ep), streamCreator)\n}\n\nfunc setUDPHandler(ctx context.Context, s *stack.Stack, streamCreator tunnel.StreamCreator) {\n\tf := udp.NewForwarder(s, func(fr *udp.ForwarderRequest) bool {\n\t\treturn forwardUDP(ctx, streamCreator, fr)\n\t})\n\ts.SetTransportProtocolHandler(udp.ProtocolNumber, f.HandlePacket)\n}\n\nfunc tcpAddrToAddr(addr tcpip.Address) netip.Addr {\n\tif addr.BitLen() == 32 {\n\t\treturn netip.AddrFrom4(addr.As4())\n\t}\n\treturn netip.AddrFrom16(addr.As16())\n}\n\nfunc newConnID(proto tcpip.TransportProtocolNumber, id stack.TransportEndpointID) tunnel.ConnID {\n\treturn tunnel.NewConnID(types.Proto(proto), netip.AddrPortFrom(tcpAddrToAddr(id.RemoteAddress), id.RemotePort), netip.AddrPortFrom(tcpAddrToAddr(id.LocalAddress), id.LocalPort))\n}\n\nfunc dispatchToStream(ctx context.Context, id tunnel.ConnID, conn net.Conn, streamCreator tunnel.StreamCreator) bool {\n\tctx, cancel := context.WithCancel(ctx)\n\tstream, err := streamCreator(ctx, id)\n\tif err != nil {\n\t\tcancel()\n\t\tswitch status.Code(err) {\n\t\tcase codes.Unavailable:\n\t\t\tif strings.HasSuffix(err.Error(), \"reading from server: EOF\") {\n\t\t\t\treturn false\n\t\t\t}\n\t\tcase codes.Canceled, codes.Aborted:\n\t\t\treturn false\n\t\t}\n\t\tclog.Errorf(ctx, \"forward %s: %v\", id, err)\n\t\treturn false\n\t}\n\tep := tunnel.NewConnEndpoint(stream, conn, cancel, nil, nil)\n\tep.Start(ctx)\n\treturn true\n}\n"
  },
  {
    "path": "pkg/vif/tunneling_device.go",
    "content": "package vif\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"net/netip\"\n\n\t\"gvisor.dev/gvisor/pkg/tcpip\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv4\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/network/ipv6\"\n\t\"gvisor.dev/gvisor/pkg/tcpip/stack\"\n\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/routing\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/tunnel\"\n)\n\ntype TunnelingDevice struct {\n\tstack  *stack.Stack\n\tnicID  tcpip.NICID\n\tDevice Device\n\tRouter *Router\n\ttable  routing.Table\n}\n\nfunc NewTunnelingDevice(ctx context.Context, tunnelStreamCreator tunnel.StreamCreator) (vif *TunnelingDevice, err error) {\n\tvar (\n\t\troutingTable routing.Table\n\t\tdev          Device\n\t\tep           stack.LinkEndpoint\n\t\tnetStack     *stack.Stack\n\t)\n\tdefer func() {\n\t\tif err != nil {\n\t\t\tif netStack != nil {\n\t\t\t\tnetStack.Close()\n\t\t\t}\n\t\t\tif ep != nil {\n\t\t\t\tep.Close()\n\t\t\t}\n\t\t\tif dev != nil {\n\t\t\t\tdev.Close()\n\t\t\t}\n\t\t\tif routingTable != nil {\n\t\t\t\troutingTable.Close(ctx)\n\t\t\t}\n\t\t}\n\t}()\n\troutingTable, err = routing.OpenTable(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdev, err = OpenTun(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tep, err = dev.NewLinkEndpoint()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tnetStack, nicID, err := NewStack(ctx, ep, tunnelStreamCreator)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trouter := NewRouter(dev, routingTable)\n\treturn &TunnelingDevice{\n\t\tstack:  netStack,\n\t\tnicID:  nicID,\n\t\tDevice: dev,\n\t\tRouter: router,\n\t\ttable:  routingTable,\n\t}, nil\n}\n\nfunc (vif *TunnelingDevice) Close(ctx context.Context) error {\n\tvif.stack.Close()\n\tvif.Router.Close(ctx)\n\tvif.Device.Close()\n\treturn vif.table.Close(ctx)\n}\n\nfunc (vif *TunnelingDevice) AddStaticNeighbor(addr netip.Addr, linkAddr net.HardwareAddr) error {\n\tvar proto tcpip.NetworkProtocolNumber\n\tvar tAddr tcpip.Address\n\tif addr.Is4() {\n\t\tproto = ipv4.ProtocolNumber\n\t\ttAddr = tcpip.AddrFrom4(addr.As4())\n\t} else {\n\t\tproto = ipv6.ProtocolNumber\n\t\ttAddr = tcpip.AddrFrom16(addr.As16())\n\t}\n\ttErr := vif.stack.AddStaticNeighbor(vif.nicID, proto, tAddr, tcpip.LinkAddress(linkAddr))\n\tif tErr != nil {\n\t\treturn errors.New(tErr.String())\n\t}\n\treturn nil\n}\n\nfunc (vif *TunnelingDevice) RemoveNeighbor(addr netip.Addr) error {\n\tvar proto tcpip.NetworkProtocolNumber\n\tvar tAddr tcpip.Address\n\tif addr.Is4() {\n\t\tproto = ipv4.ProtocolNumber\n\t\ttAddr = tcpip.AddrFrom4(addr.As4())\n\t} else {\n\t\tproto = ipv6.ProtocolNumber\n\t\ttAddr = tcpip.AddrFrom16(addr.As16())\n\t}\n\ttErr := vif.stack.RemoveNeighbor(vif.nicID, proto, tAddr)\n\tif tErr != nil {\n\t\treturn errors.New(tErr.String())\n\t}\n\treturn nil\n}\n\nfunc (vif *TunnelingDevice) DialTCP(ctx context.Context, addr netip.AddrPort) (net.Conn, error) {\n\tp, a := vif.toFullAddr(addr)\n\treturn gonet.DialContextTCP(ctx, vif.stack, a, p)\n}\n\nfunc (vif *TunnelingDevice) DialUDP(_ context.Context, addr, returnAddr netip.AddrPort) (net.Conn, error) {\n\tvar fa, rfa *tcpip.FullAddress\n\tvar p tcpip.NetworkProtocolNumber\n\tif addr.IsValid() {\n\t\tvar a tcpip.FullAddress\n\t\tp, a = vif.toFullAddr(addr)\n\t\tfa = &a\n\t}\n\tif returnAddr.IsValid() {\n\t\tvar a tcpip.FullAddress\n\t\t_, a = vif.toFullAddr(addr)\n\t\tfa = &a\n\t}\n\treturn gonet.DialUDP(vif.stack, fa, rfa, p)\n}\n\nfunc (vif *TunnelingDevice) Run(ctx context.Context) (err error) {\n\tvif.stack.Wait()\n\tclog.Debug(ctx, \"VIF ended\")\n\treturn nil\n}\n\nfunc (vif *TunnelingDevice) toFullAddr(ap netip.AddrPort) (tcpip.NetworkProtocolNumber, tcpip.FullAddress) {\n\tfa := tcpip.FullAddress{\n\t\tNIC:  vif.nicID,\n\t\tPort: ap.Port(),\n\t}\n\ta := ap.Addr()\n\tvar p tcpip.NetworkProtocolNumber\n\tif a.Is4() {\n\t\tp = ipv4.ProtocolNumber\n\t\tfa.Addr = tcpip.AddrFrom4(a.As4())\n\t} else {\n\t\tp = ipv6.ProtocolNumber\n\t\tfa.Addr = tcpip.AddrFrom16(a.As16())\n\t}\n\treturn p, fa\n}\n"
  },
  {
    "path": "pkg/workload/informers.go",
    "content": "package workload\n\nimport (\n\t\"context\"\n\n\tapps \"k8s.io/api/apps/v1\"\n\tcore \"k8s.io/api/core/v1\"\n\t\"k8s.io/client-go/tools/cache\"\n\n\targorollouts \"github.com/datawire/argo-rollouts-go-client/pkg/apis/rollouts/v1alpha1\"\n\t\"github.com/telepresenceio/clog\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/informer\"\n)\n\nfunc whereWeWatch(ns string) string {\n\tif ns == \"\" {\n\t\treturn \"cluster wide\"\n\t}\n\treturn \"in namespace \" + ns\n}\n\nfunc StartDeployments(ctx context.Context, ns string) cache.SharedIndexInformer {\n\tf := informer.GetK8sFactory(ctx, ns)\n\tix := f.Apps().V1().Deployments().Informer()\n\t_ = ix.SetTransform(func(o any) (any, error) {\n\t\t// Strip the parts of the deployment that we don't care about to save memory\n\t\tif dep, ok := o.(*apps.Deployment); ok {\n\t\t\tom := &dep.ObjectMeta\n\t\t\tif an := om.Annotations; an != nil {\n\t\t\t\tdelete(an, core.LastAppliedConfigAnnotation)\n\t\t\t}\n\t\t\tdep.ManagedFields = nil\n\t\t\tdep.Finalizers = nil\n\t\t\tdep.OwnerReferences = nil\n\t\t}\n\t\treturn o, nil\n\t})\n\t_ = ix.SetWatchErrorHandler(func(_ *cache.Reflector, err error) {\n\t\tclog.Errorf(ctx, \"watcher for Deployments %s: %v\", whereWeWatch(ns), err)\n\t})\n\treturn ix\n}\n\nfunc StartReplicaSets(ctx context.Context, ns string) cache.SharedIndexInformer {\n\tf := informer.GetK8sFactory(ctx, ns)\n\tix := f.Apps().V1().ReplicaSets().Informer()\n\t_ = ix.SetTransform(func(o any) (any, error) {\n\t\t// Strip the parts of the replicaset that we don't care about. Saves memory\n\t\tif dep, ok := o.(*apps.ReplicaSet); ok {\n\t\t\tom := &dep.ObjectMeta\n\t\t\tif an := om.Annotations; an != nil {\n\t\t\t\tdelete(an, core.LastAppliedConfigAnnotation)\n\t\t\t}\n\t\t\tdep.ManagedFields = nil\n\t\t\tdep.Finalizers = nil\n\t\t}\n\t\treturn o, nil\n\t})\n\t_ = ix.SetWatchErrorHandler(func(_ *cache.Reflector, err error) {\n\t\tclog.Errorf(ctx, \"watcher for ReplicaSets %s: %v\", whereWeWatch(ns), err)\n\t})\n\treturn ix\n}\n\nfunc StartStatefulSets(ctx context.Context, ns string) cache.SharedIndexInformer {\n\tf := informer.GetK8sFactory(ctx, ns)\n\tix := f.Apps().V1().StatefulSets().Informer()\n\t_ = ix.SetTransform(func(o any) (any, error) {\n\t\t// Strip the parts of the stateful that we don't care about. Saves memory\n\t\tif dep, ok := o.(*apps.StatefulSet); ok {\n\t\t\tom := &dep.ObjectMeta\n\t\t\tif an := om.Annotations; an != nil {\n\t\t\t\tdelete(an, core.LastAppliedConfigAnnotation)\n\t\t\t}\n\t\t\tdep.ManagedFields = nil\n\t\t\tdep.Finalizers = nil\n\t\t}\n\t\treturn o, nil\n\t})\n\t_ = ix.SetWatchErrorHandler(func(_ *cache.Reflector, err error) {\n\t\tclog.Errorf(ctx, \"watcher for StatefulSet %s: %v\", whereWeWatch(ns), err)\n\t})\n\treturn ix\n}\n\nfunc StartRollouts(ctx context.Context, ns string) cache.SharedIndexInformer {\n\tf := informer.GetArgoRolloutsFactory(ctx, ns)\n\tclog.Infof(ctx, \"Watching Rollouts in %s\", ns)\n\tix := f.Argoproj().V1alpha1().Rollouts().Informer()\n\t_ = ix.SetTransform(func(o any) (any, error) {\n\t\t// Strip the parts of the rollout that we don't care about. Saves memory\n\t\tif dep, ok := o.(*argorollouts.Rollout); ok {\n\t\t\tom := &dep.ObjectMeta\n\t\t\tif an := om.Annotations; an != nil {\n\t\t\t\tdelete(an, core.LastAppliedConfigAnnotation)\n\t\t\t}\n\t\t\tdep.ManagedFields = nil\n\t\t\tdep.Finalizers = nil\n\t\t}\n\t\treturn o, nil\n\t})\n\t_ = ix.SetWatchErrorHandler(func(_ *cache.Reflector, err error) {\n\t\tclog.Errorf(ctx, \"watcher for Rollouts %s: %v\", whereWeWatch(ns), err)\n\t})\n\treturn ix\n}\n"
  },
  {
    "path": "pkg/workload/state.go",
    "content": "package workload\n\nimport (\n\t\"slices\"\n\n\tappsv1 \"k8s.io/api/apps/v1\"\n\n\targorollouts \"github.com/datawire/argo-rollouts-go-client/pkg/apis/rollouts/v1alpha1\"\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\ntype State int\n\nconst (\n\tStateUnknown State = iota\n\tStateProgressing\n\tStateAvailable\n\tStateFailure\n)\n\nfunc deploymentState(d *appsv1.Deployment) State {\n\tconds := d.Status.Conditions\n\tif slices.ContainsFunc(conds, func(c appsv1.DeploymentCondition) bool {\n\t\treturn c.Status == \"True\" && c.Type == appsv1.DeploymentReplicaFailure\n\t}) {\n\t\treturn StateFailure\n\t}\n\tif slices.ContainsFunc(conds, func(c appsv1.DeploymentCondition) bool {\n\t\treturn c.Status == \"True\" && c.Type == appsv1.DeploymentAvailable\n\t}) {\n\t\treturn StateAvailable\n\t}\n\tif slices.ContainsFunc(conds, func(c appsv1.DeploymentCondition) bool {\n\t\treturn c.Status == \"True\" && c.Type == appsv1.DeploymentProgressing\n\t}) {\n\t\treturn StateProgressing\n\t}\n\treturn StateUnknown\n}\n\nfunc replicaSetState(d *appsv1.ReplicaSet) State {\n\tif slices.ContainsFunc(d.Status.Conditions, func(c appsv1.ReplicaSetCondition) bool {\n\t\treturn c.Status == \"True\" && c.Type == appsv1.ReplicaSetReplicaFailure\n\t}) {\n\t\treturn StateFailure\n\t}\n\treturn StateAvailable\n}\n\nfunc statefulSetState(_ *appsv1.StatefulSet) State {\n\treturn StateAvailable\n}\n\nfunc rolloutSetState(r *argorollouts.Rollout) State {\n\tconds := r.Status.Conditions\n\tif slices.ContainsFunc(conds, func(c argorollouts.RolloutCondition) bool {\n\t\treturn c.Status == \"True\" && c.Type == argorollouts.RolloutReplicaFailure\n\t}) {\n\t\treturn StateFailure\n\t}\n\tif slices.ContainsFunc(conds, func(c argorollouts.RolloutCondition) bool {\n\t\treturn c.Status == \"True\" && c.Type == argorollouts.RolloutAvailable\n\t}) {\n\t\treturn StateAvailable\n\t}\n\tif slices.ContainsFunc(conds, func(c argorollouts.RolloutCondition) bool {\n\t\treturn c.Status == \"True\" && c.Type == argorollouts.RolloutProgressing\n\t}) {\n\t\treturn StateProgressing\n\t}\n\treturn StateUnknown\n}\n\nfunc (ws State) String() string {\n\tswitch ws {\n\tcase StateProgressing:\n\t\treturn \"Progressing\"\n\tcase StateAvailable:\n\t\treturn \"Available\"\n\tcase StateFailure:\n\t\treturn \"Failure\"\n\tdefault:\n\t\treturn \"Unknown\"\n\t}\n}\n\nfunc GetWorkloadState(wl k8sapi.Workload) State {\n\tif d, ok := k8sapi.DeploymentImpl(wl); ok {\n\t\treturn deploymentState(d)\n\t}\n\tif r, ok := k8sapi.ReplicaSetImpl(wl); ok {\n\t\treturn replicaSetState(r)\n\t}\n\tif s, ok := k8sapi.StatefulSetImpl(wl); ok {\n\t\treturn statefulSetState(s)\n\t}\n\tif rt, ok := k8sapi.RolloutImpl(wl); ok {\n\t\treturn rolloutSetState(rt)\n\t}\n\treturn StateUnknown\n}\n\nfunc StateFromRPC(s manager.WorkloadInfo_State) State {\n\tswitch s {\n\tcase manager.WorkloadInfo_AVAILABLE:\n\t\treturn StateAvailable\n\tcase manager.WorkloadInfo_FAILURE:\n\t\treturn StateFailure\n\tcase manager.WorkloadInfo_PROGRESSING:\n\t\treturn StateProgressing\n\tdefault:\n\t\treturn StateUnknown\n\t}\n}\n"
  },
  {
    "path": "pkg/workload/util.go",
    "content": "package workload\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\n\t\"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\t\"github.com/telepresenceio/telepresence/v2/pkg/k8sapi\"\n)\n\nfunc FromAny(obj any) (k8sapi.Workload, bool) {\n\tif ro, ok := obj.(runtime.Object); ok {\n\t\tif wl, err := k8sapi.WrapWorkload(ro); err == nil {\n\t\t\treturn wl, true\n\t\t}\n\t}\n\treturn nil, false\n}\n\nfunc RpcKind(s k8sapi.Kind) manager.WorkloadInfo_Kind {\n\tswitch s {\n\tcase k8sapi.DeploymentKind:\n\t\treturn manager.WorkloadInfo_DEPLOYMENT\n\tcase k8sapi.ReplicaSetKind:\n\t\treturn manager.WorkloadInfo_REPLICASET\n\tcase k8sapi.StatefulSetKind:\n\t\treturn manager.WorkloadInfo_STATEFULSET\n\tcase k8sapi.RolloutKind:\n\t\treturn manager.WorkloadInfo_ROLLOUT\n\tdefault:\n\t\treturn manager.WorkloadInfo_UNSPECIFIED\n\t}\n}\n"
  },
  {
    "path": "rpc/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\n"
  },
  {
    "path": "rpc/agent/agent.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.30.2\n// source: agent/agent.proto\n\n// The \"agent\" package describes the server implemented by the\n// in-cluster Agent\n\npackage agent\n\nimport (\n\tmanager \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n\treflect \"reflect\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\nvar File_agent_agent_proto protoreflect.FileDescriptor\n\nconst file_agent_agent_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x11agent/agent.proto\\x12\\x12telepresence.agent\\x1a\\x1bgoogle/protobuf/empty.proto\\x1a\\x15manager/manager.proto2\\xd0\\x02\\n\" +\n\t\"\\x05Agent\\x12S\\n\" +\n\t\"\\x06Lookup\\x12#.telepresence.manager.LookupRequest\\x1a$.telepresence.manager.LookupResponse\\x12V\\n\" +\n\t\"\\x06Tunnel\\x12#.telepresence.manager.TunnelMessage\\x1a#.telepresence.manager.TunnelMessage(\\x010\\x01\\x12E\\n\" +\n\t\"\\aVersion\\x12\\x16.google.protobuf.Empty\\x1a\\\".telepresence.manager.VersionInfo2\\x12S\\n\" +\n\t\"\\tWatchDial\\x12!.telepresence.manager.SessionInfo\\x1a!.telepresence.manager.DialRequest0\\x01B5Z3github.com/telepresenceio/telepresence/rpc/v2/agentb\\x06proto3\"\n\nvar file_agent_agent_proto_goTypes = []any{\n\t(*manager.LookupRequest)(nil),  // 0: telepresence.manager.LookupRequest\n\t(*manager.TunnelMessage)(nil),  // 1: telepresence.manager.TunnelMessage\n\t(*emptypb.Empty)(nil),          // 2: google.protobuf.Empty\n\t(*manager.SessionInfo)(nil),    // 3: telepresence.manager.SessionInfo\n\t(*manager.LookupResponse)(nil), // 4: telepresence.manager.LookupResponse\n\t(*manager.VersionInfo2)(nil),   // 5: telepresence.manager.VersionInfo2\n\t(*manager.DialRequest)(nil),    // 6: telepresence.manager.DialRequest\n}\nvar file_agent_agent_proto_depIdxs = []int32{\n\t0, // 0: telepresence.agent.Agent.Lookup:input_type -> telepresence.manager.LookupRequest\n\t1, // 1: telepresence.agent.Agent.Tunnel:input_type -> telepresence.manager.TunnelMessage\n\t2, // 2: telepresence.agent.Agent.Version:input_type -> google.protobuf.Empty\n\t3, // 3: telepresence.agent.Agent.WatchDial:input_type -> telepresence.manager.SessionInfo\n\t4, // 4: telepresence.agent.Agent.Lookup:output_type -> telepresence.manager.LookupResponse\n\t1, // 5: telepresence.agent.Agent.Tunnel:output_type -> telepresence.manager.TunnelMessage\n\t5, // 6: telepresence.agent.Agent.Version:output_type -> telepresence.manager.VersionInfo2\n\t6, // 7: telepresence.agent.Agent.WatchDial:output_type -> telepresence.manager.DialRequest\n\t4, // [4:8] is the sub-list for method output_type\n\t0, // [0:4] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_agent_agent_proto_init() }\nfunc file_agent_agent_proto_init() {\n\tif File_agent_agent_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_agent_agent_proto_rawDesc), len(file_agent_agent_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   0,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_agent_agent_proto_goTypes,\n\t\tDependencyIndexes: file_agent_agent_proto_depIdxs,\n\t}.Build()\n\tFile_agent_agent_proto = out.File\n\tfile_agent_agent_proto_goTypes = nil\n\tfile_agent_agent_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "rpc/agent/agent.proto",
    "content": "syntax = \"proto3\";\n\n// The \"agent\" package describes the server implemented by the\n// in-cluster Agent\npackage telepresence.agent;\n\nimport \"google/protobuf/empty.proto\";\nimport \"manager/manager.proto\";\n\noption go_package = \"github.com/telepresenceio/telepresence/rpc/v2/agent\";\n\nservice Agent {\n  // Lookup performs a LookupIP in the cluster.\n  rpc Lookup(manager.LookupRequest) returns (manager.LookupResponse);\n\n  rpc Tunnel(stream manager.TunnelMessage) returns (stream manager.TunnelMessage);\n\n  // Version returns the version information of the Manager.\n  rpc Version(google.protobuf.Empty) returns (manager.VersionInfo2);\n\n  // WatchDial makes it possible for the client side to receive DialRequests\n  // from the traffic-agent. Requests are sent when an intercepted agent needs\n  // a Tunnel to the Telepresence client on the workstation. The receiver of\n  // the request dials a connection and responds with the needed Tunnel.\n  rpc WatchDial(manager.SessionInfo) returns (stream manager.DialRequest);\n}"
  },
  {
    "path": "rpc/agent/agent_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.1\n// - protoc             v6.30.2\n// source: agent/agent.proto\n\n// The \"agent\" package describes the server implemented by the\n// in-cluster Agent\n\npackage agent\n\nimport (\n\tcontext \"context\"\n\tmanager \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tAgent_Lookup_FullMethodName    = \"/telepresence.agent.Agent/Lookup\"\n\tAgent_Tunnel_FullMethodName    = \"/telepresence.agent.Agent/Tunnel\"\n\tAgent_Version_FullMethodName   = \"/telepresence.agent.Agent/Version\"\n\tAgent_WatchDial_FullMethodName = \"/telepresence.agent.Agent/WatchDial\"\n)\n\n// AgentClient is the client API for Agent service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype AgentClient interface {\n\t// Lookup performs a LookupIP in the cluster.\n\tLookup(ctx context.Context, in *manager.LookupRequest, opts ...grpc.CallOption) (*manager.LookupResponse, error)\n\tTunnel(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[manager.TunnelMessage, manager.TunnelMessage], error)\n\t// Version returns the version information of the Manager.\n\tVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*manager.VersionInfo2, error)\n\t// WatchDial makes it possible for the client side to receive DialRequests\n\t// from the traffic-agent. Requests are sent when an intercepted agent needs\n\t// a Tunnel to the Telepresence client on the workstation. The receiver of\n\t// the request dials a connection and responds with the needed Tunnel.\n\tWatchDial(ctx context.Context, in *manager.SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[manager.DialRequest], error)\n}\n\ntype agentClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewAgentClient(cc grpc.ClientConnInterface) AgentClient {\n\treturn &agentClient{cc}\n}\n\nfunc (c *agentClient) Lookup(ctx context.Context, in *manager.LookupRequest, opts ...grpc.CallOption) (*manager.LookupResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(manager.LookupResponse)\n\terr := c.cc.Invoke(ctx, Agent_Lookup_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *agentClient) Tunnel(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[manager.TunnelMessage, manager.TunnelMessage], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Agent_ServiceDesc.Streams[0], Agent_Tunnel_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[manager.TunnelMessage, manager.TunnelMessage]{ClientStream: stream}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Agent_TunnelClient = grpc.BidiStreamingClient[manager.TunnelMessage, manager.TunnelMessage]\n\nfunc (c *agentClient) Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*manager.VersionInfo2, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(manager.VersionInfo2)\n\terr := c.cc.Invoke(ctx, Agent_Version_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *agentClient) WatchDial(ctx context.Context, in *manager.SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[manager.DialRequest], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Agent_ServiceDesc.Streams[1], Agent_WatchDial_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[manager.SessionInfo, manager.DialRequest]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Agent_WatchDialClient = grpc.ServerStreamingClient[manager.DialRequest]\n\n// AgentServer is the server API for Agent service.\n// All implementations must embed UnimplementedAgentServer\n// for forward compatibility.\ntype AgentServer interface {\n\t// Lookup performs a LookupIP in the cluster.\n\tLookup(context.Context, *manager.LookupRequest) (*manager.LookupResponse, error)\n\tTunnel(grpc.BidiStreamingServer[manager.TunnelMessage, manager.TunnelMessage]) error\n\t// Version returns the version information of the Manager.\n\tVersion(context.Context, *emptypb.Empty) (*manager.VersionInfo2, error)\n\t// WatchDial makes it possible for the client side to receive DialRequests\n\t// from the traffic-agent. Requests are sent when an intercepted agent needs\n\t// a Tunnel to the Telepresence client on the workstation. The receiver of\n\t// the request dials a connection and responds with the needed Tunnel.\n\tWatchDial(*manager.SessionInfo, grpc.ServerStreamingServer[manager.DialRequest]) error\n\tmustEmbedUnimplementedAgentServer()\n}\n\n// UnimplementedAgentServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedAgentServer struct{}\n\nfunc (UnimplementedAgentServer) Lookup(context.Context, *manager.LookupRequest) (*manager.LookupResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Lookup not implemented\")\n}\nfunc (UnimplementedAgentServer) Tunnel(grpc.BidiStreamingServer[manager.TunnelMessage, manager.TunnelMessage]) error {\n\treturn status.Error(codes.Unimplemented, \"method Tunnel not implemented\")\n}\nfunc (UnimplementedAgentServer) Version(context.Context, *emptypb.Empty) (*manager.VersionInfo2, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Version not implemented\")\n}\nfunc (UnimplementedAgentServer) WatchDial(*manager.SessionInfo, grpc.ServerStreamingServer[manager.DialRequest]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchDial not implemented\")\n}\nfunc (UnimplementedAgentServer) mustEmbedUnimplementedAgentServer() {}\nfunc (UnimplementedAgentServer) testEmbeddedByValue()               {}\n\n// UnsafeAgentServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to AgentServer will\n// result in compilation errors.\ntype UnsafeAgentServer interface {\n\tmustEmbedUnimplementedAgentServer()\n}\n\nfunc RegisterAgentServer(s grpc.ServiceRegistrar, srv AgentServer) {\n\t// If the following call panics, it indicates UnimplementedAgentServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&Agent_ServiceDesc, srv)\n}\n\nfunc _Agent_Lookup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(manager.LookupRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AgentServer).Lookup(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Agent_Lookup_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AgentServer).Lookup(ctx, req.(*manager.LookupRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Agent_Tunnel_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(AgentServer).Tunnel(&grpc.GenericServerStream[manager.TunnelMessage, manager.TunnelMessage]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Agent_TunnelServer = grpc.BidiStreamingServer[manager.TunnelMessage, manager.TunnelMessage]\n\nfunc _Agent_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AgentServer).Version(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Agent_Version_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AgentServer).Version(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Agent_WatchDial_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(manager.SessionInfo)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(AgentServer).WatchDial(m, &grpc.GenericServerStream[manager.SessionInfo, manager.DialRequest]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Agent_WatchDialServer = grpc.ServerStreamingServer[manager.DialRequest]\n\n// Agent_ServiceDesc is the grpc.ServiceDesc for Agent service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar Agent_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"telepresence.agent.Agent\",\n\tHandlerType: (*AgentServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Lookup\",\n\t\t\tHandler:    _Agent_Lookup_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Version\",\n\t\t\tHandler:    _Agent_Version_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"Tunnel\",\n\t\t\tHandler:       _Agent_Tunnel_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchDial\",\n\t\t\tHandler:       _Agent_WatchDial_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"agent/agent.proto\",\n}\n"
  },
  {
    "path": "rpc/authenticator/authenticator.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.30.2\n// source: authenticator/authenticator.proto\n\npackage authenticator\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype GetContextExecCredentialsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tContextName   string                 `protobuf:\"bytes,1,opt,name=context_name,json=contextName,proto3\" json:\"context_name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetContextExecCredentialsRequest) Reset() {\n\t*x = GetContextExecCredentialsRequest{}\n\tmi := &file_authenticator_authenticator_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetContextExecCredentialsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetContextExecCredentialsRequest) ProtoMessage() {}\n\nfunc (x *GetContextExecCredentialsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_authenticator_authenticator_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetContextExecCredentialsRequest.ProtoReflect.Descriptor instead.\nfunc (*GetContextExecCredentialsRequest) Descriptor() ([]byte, []int) {\n\treturn file_authenticator_authenticator_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *GetContextExecCredentialsRequest) GetContextName() string {\n\tif x != nil {\n\t\treturn x.ContextName\n\t}\n\treturn \"\"\n}\n\ntype GetContextExecCredentialsResponse struct {\n\tstate          protoimpl.MessageState `protogen:\"open.v1\"`\n\tRawCredentials []byte                 `protobuf:\"bytes,1,opt,name=raw_credentials,json=rawCredentials,proto3\" json:\"raw_credentials,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *GetContextExecCredentialsResponse) Reset() {\n\t*x = GetContextExecCredentialsResponse{}\n\tmi := &file_authenticator_authenticator_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetContextExecCredentialsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetContextExecCredentialsResponse) ProtoMessage() {}\n\nfunc (x *GetContextExecCredentialsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_authenticator_authenticator_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetContextExecCredentialsResponse.ProtoReflect.Descriptor instead.\nfunc (*GetContextExecCredentialsResponse) Descriptor() ([]byte, []int) {\n\treturn file_authenticator_authenticator_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *GetContextExecCredentialsResponse) GetRawCredentials() []byte {\n\tif x != nil {\n\t\treturn x.RawCredentials\n\t}\n\treturn nil\n}\n\nvar File_authenticator_authenticator_proto protoreflect.FileDescriptor\n\nconst file_authenticator_authenticator_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"!authenticator/authenticator.proto\\x12\\x1atelepresence.authenticator\\\"E\\n\" +\n\t\" GetContextExecCredentialsRequest\\x12!\\n\" +\n\t\"\\fcontext_name\\x18\\x01 \\x01(\\tR\\vcontextName\\\"L\\n\" +\n\t\"!GetContextExecCredentialsResponse\\x12'\\n\" +\n\t\"\\x0fraw_credentials\\x18\\x01 \\x01(\\fR\\x0erawCredentials2\\xaa\\x01\\n\" +\n\t\"\\rAuthenticator\\x12\\x98\\x01\\n\" +\n\t\"\\x19GetContextExecCredentials\\x12<.telepresence.authenticator.GetContextExecCredentialsRequest\\x1a=.telepresence.authenticator.GetContextExecCredentialsResponseB=Z;github.com/telepresenceio/telepresence/rpc/v2/authenticatorb\\x06proto3\"\n\nvar (\n\tfile_authenticator_authenticator_proto_rawDescOnce sync.Once\n\tfile_authenticator_authenticator_proto_rawDescData []byte\n)\n\nfunc file_authenticator_authenticator_proto_rawDescGZIP() []byte {\n\tfile_authenticator_authenticator_proto_rawDescOnce.Do(func() {\n\t\tfile_authenticator_authenticator_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_authenticator_authenticator_proto_rawDesc), len(file_authenticator_authenticator_proto_rawDesc)))\n\t})\n\treturn file_authenticator_authenticator_proto_rawDescData\n}\n\nvar file_authenticator_authenticator_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_authenticator_authenticator_proto_goTypes = []any{\n\t(*GetContextExecCredentialsRequest)(nil),  // 0: telepresence.authenticator.GetContextExecCredentialsRequest\n\t(*GetContextExecCredentialsResponse)(nil), // 1: telepresence.authenticator.GetContextExecCredentialsResponse\n}\nvar file_authenticator_authenticator_proto_depIdxs = []int32{\n\t0, // 0: telepresence.authenticator.Authenticator.GetContextExecCredentials:input_type -> telepresence.authenticator.GetContextExecCredentialsRequest\n\t1, // 1: telepresence.authenticator.Authenticator.GetContextExecCredentials:output_type -> telepresence.authenticator.GetContextExecCredentialsResponse\n\t1, // [1:2] is the sub-list for method output_type\n\t0, // [0:1] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_authenticator_authenticator_proto_init() }\nfunc file_authenticator_authenticator_proto_init() {\n\tif File_authenticator_authenticator_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_authenticator_authenticator_proto_rawDesc), len(file_authenticator_authenticator_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_authenticator_authenticator_proto_goTypes,\n\t\tDependencyIndexes: file_authenticator_authenticator_proto_depIdxs,\n\t\tMessageInfos:      file_authenticator_authenticator_proto_msgTypes,\n\t}.Build()\n\tFile_authenticator_authenticator_proto = out.File\n\tfile_authenticator_authenticator_proto_goTypes = nil\n\tfile_authenticator_authenticator_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "rpc/authenticator/authenticator.proto",
    "content": "syntax = \"proto3\";\n\npackage telepresence.authenticator;\n\noption go_package = \"github.com/telepresenceio/telepresence/rpc/v2/authenticator\";\n\nservice Authenticator {\n  rpc GetContextExecCredentials(GetContextExecCredentialsRequest) returns (GetContextExecCredentialsResponse);\n}\n\nmessage GetContextExecCredentialsRequest {\n  string context_name = 1;\n}\n\nmessage GetContextExecCredentialsResponse {\n  bytes raw_credentials = 1;\n}\n\n"
  },
  {
    "path": "rpc/authenticator/authenticator_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.1\n// - protoc             v6.30.2\n// source: authenticator/authenticator.proto\n\npackage authenticator\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tAuthenticator_GetContextExecCredentials_FullMethodName = \"/telepresence.authenticator.Authenticator/GetContextExecCredentials\"\n)\n\n// AuthenticatorClient is the client API for Authenticator service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype AuthenticatorClient interface {\n\tGetContextExecCredentials(ctx context.Context, in *GetContextExecCredentialsRequest, opts ...grpc.CallOption) (*GetContextExecCredentialsResponse, error)\n}\n\ntype authenticatorClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewAuthenticatorClient(cc grpc.ClientConnInterface) AuthenticatorClient {\n\treturn &authenticatorClient{cc}\n}\n\nfunc (c *authenticatorClient) GetContextExecCredentials(ctx context.Context, in *GetContextExecCredentialsRequest, opts ...grpc.CallOption) (*GetContextExecCredentialsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetContextExecCredentialsResponse)\n\terr := c.cc.Invoke(ctx, Authenticator_GetContextExecCredentials_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// AuthenticatorServer is the server API for Authenticator service.\n// All implementations must embed UnimplementedAuthenticatorServer\n// for forward compatibility.\ntype AuthenticatorServer interface {\n\tGetContextExecCredentials(context.Context, *GetContextExecCredentialsRequest) (*GetContextExecCredentialsResponse, error)\n\tmustEmbedUnimplementedAuthenticatorServer()\n}\n\n// UnimplementedAuthenticatorServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedAuthenticatorServer struct{}\n\nfunc (UnimplementedAuthenticatorServer) GetContextExecCredentials(context.Context, *GetContextExecCredentialsRequest) (*GetContextExecCredentialsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetContextExecCredentials not implemented\")\n}\nfunc (UnimplementedAuthenticatorServer) mustEmbedUnimplementedAuthenticatorServer() {}\nfunc (UnimplementedAuthenticatorServer) testEmbeddedByValue()                       {}\n\n// UnsafeAuthenticatorServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to AuthenticatorServer will\n// result in compilation errors.\ntype UnsafeAuthenticatorServer interface {\n\tmustEmbedUnimplementedAuthenticatorServer()\n}\n\nfunc RegisterAuthenticatorServer(s grpc.ServiceRegistrar, srv AuthenticatorServer) {\n\t// If the following call panics, it indicates UnimplementedAuthenticatorServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&Authenticator_ServiceDesc, srv)\n}\n\nfunc _Authenticator_GetContextExecCredentials_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetContextExecCredentialsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(AuthenticatorServer).GetContextExecCredentials(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Authenticator_GetContextExecCredentials_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(AuthenticatorServer).GetContextExecCredentials(ctx, req.(*GetContextExecCredentialsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// Authenticator_ServiceDesc is the grpc.ServiceDesc for Authenticator service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar Authenticator_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"telepresence.authenticator.Authenticator\",\n\tHandlerType: (*AuthenticatorServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetContextExecCredentials\",\n\t\t\tHandler:    _Authenticator_GetContextExecCredentials_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"authenticator/authenticator.proto\",\n}\n"
  },
  {
    "path": "rpc/common/version.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.30.2\n// source: common/version.proto\n\npackage common\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// VersionInfo is the type that both `telepresence daemon` (the super-user\n// daemon) and `telepresence conector` (the normal-user daemon) use\n// when reporting their version to the user-facing CLI.\ntype VersionInfo struct {\n\tstate      protoimpl.MessageState `protogen:\"open.v1\"`\n\tApiVersion int32                  `protobuf:\"varint,1,opt,name=api_version,json=apiVersion,proto3\" json:\"api_version,omitempty\"`\n\t// Version is a \"vSEMVER\" string of the product version number.\n\tVersion string `protobuf:\"bytes,2,opt,name=version,proto3\" json:\"version,omitempty\"`\n\t// Executable is the path to the executable for the process.\n\tExecutable string `protobuf:\"bytes,3,opt,name=executable,proto3\" json:\"executable,omitempty\"`\n\t// Name of the process (Client, User Daemon, Root Daemon, Traffic Manager)\n\tName          string `protobuf:\"bytes,4,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VersionInfo) Reset() {\n\t*x = VersionInfo{}\n\tmi := &file_common_version_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionInfo) ProtoMessage() {}\n\nfunc (x *VersionInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_common_version_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VersionInfo.ProtoReflect.Descriptor instead.\nfunc (*VersionInfo) Descriptor() ([]byte, []int) {\n\treturn file_common_version_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *VersionInfo) GetApiVersion() int32 {\n\tif x != nil {\n\t\treturn x.ApiVersion\n\t}\n\treturn 0\n}\n\nfunc (x *VersionInfo) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\nfunc (x *VersionInfo) GetExecutable() string {\n\tif x != nil {\n\t\treturn x.Executable\n\t}\n\treturn \"\"\n}\n\nfunc (x *VersionInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nvar File_common_version_proto protoreflect.FileDescriptor\n\nconst file_common_version_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x14common/version.proto\\x12\\x13telepresence.common\\\"|\\n\" +\n\t\"\\vVersionInfo\\x12\\x1f\\n\" +\n\t\"\\vapi_version\\x18\\x01 \\x01(\\x05R\\n\" +\n\t\"apiVersion\\x12\\x18\\n\" +\n\t\"\\aversion\\x18\\x02 \\x01(\\tR\\aversion\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"executable\\x18\\x03 \\x01(\\tR\\n\" +\n\t\"executable\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x04 \\x01(\\tR\\x04nameB6Z4github.com/telepresenceio/telepresence/rpc/v2/commonb\\x06proto3\"\n\nvar (\n\tfile_common_version_proto_rawDescOnce sync.Once\n\tfile_common_version_proto_rawDescData []byte\n)\n\nfunc file_common_version_proto_rawDescGZIP() []byte {\n\tfile_common_version_proto_rawDescOnce.Do(func() {\n\t\tfile_common_version_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_common_version_proto_rawDesc), len(file_common_version_proto_rawDesc)))\n\t})\n\treturn file_common_version_proto_rawDescData\n}\n\nvar file_common_version_proto_msgTypes = make([]protoimpl.MessageInfo, 1)\nvar file_common_version_proto_goTypes = []any{\n\t(*VersionInfo)(nil), // 0: telepresence.common.VersionInfo\n}\nvar file_common_version_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_common_version_proto_init() }\nfunc file_common_version_proto_init() {\n\tif File_common_version_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_common_version_proto_rawDesc), len(file_common_version_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   1,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_common_version_proto_goTypes,\n\t\tDependencyIndexes: file_common_version_proto_depIdxs,\n\t\tMessageInfos:      file_common_version_proto_msgTypes,\n\t}.Build()\n\tFile_common_version_proto = out.File\n\tfile_common_version_proto_goTypes = nil\n\tfile_common_version_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "rpc/common/version.proto",
    "content": "syntax = \"proto3\";\npackage telepresence.common;\n\noption go_package = \"github.com/telepresenceio/telepresence/rpc/v2/common\";\n\n// VersionInfo is the type that both `telepresence daemon` (the super-user\n// daemon) and `telepresence conector` (the normal-user daemon) use\n// when reporting their version to the user-facing CLI.\nmessage VersionInfo {\n  int32 api_version = 1;\n\n  // Version is a \"vSEMVER\" string of the product version number.\n  string version = 2;\n \n  // Executable is the path to the executable for the process.\n  string executable = 3;\n\n  // Name of the process (Client, User Daemon, Root Daemon, Traffic Manager)\n  string name = 4;\n}\n"
  },
  {
    "path": "rpc/connector/connector.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.30.2\n// source: connector/connector.proto\n\npackage connector\n\nimport (\n\tcommon \"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\tdaemon \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\tmanager \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\tdurationpb \"google.golang.org/protobuf/types/known/durationpb\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype UninstallRequest_UninstallType int32\n\nconst (\n\tUninstallRequest_UNSPECIFIED UninstallRequest_UninstallType = 0\n\t// Uninstalls an agent from the named workloads\n\tUninstallRequest_NAMED_AGENTS UninstallRequest_UninstallType = 1\n\t// Uninstalls all agents\n\tUninstallRequest_ALL_AGENTS UninstallRequest_UninstallType = 2\n)\n\n// Enum value maps for UninstallRequest_UninstallType.\nvar (\n\tUninstallRequest_UninstallType_name = map[int32]string{\n\t\t0: \"UNSPECIFIED\",\n\t\t1: \"NAMED_AGENTS\",\n\t\t2: \"ALL_AGENTS\",\n\t}\n\tUninstallRequest_UninstallType_value = map[string]int32{\n\t\t\"UNSPECIFIED\":  0,\n\t\t\"NAMED_AGENTS\": 1,\n\t\t\"ALL_AGENTS\":   2,\n\t}\n)\n\nfunc (x UninstallRequest_UninstallType) Enum() *UninstallRequest_UninstallType {\n\tp := new(UninstallRequest_UninstallType)\n\t*p = x\n\treturn p\n}\n\nfunc (x UninstallRequest_UninstallType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (UninstallRequest_UninstallType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_connector_connector_proto_enumTypes[0].Descriptor()\n}\n\nfunc (UninstallRequest_UninstallType) Type() protoreflect.EnumType {\n\treturn &file_connector_connector_proto_enumTypes[0]\n}\n\nfunc (x UninstallRequest_UninstallType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use UninstallRequest_UninstallType.Descriptor instead.\nfunc (UninstallRequest_UninstallType) EnumDescriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{3, 0}\n}\n\n// Bitmap filter\ntype ListRequest_Filter int32\n\nconst (\n\tListRequest_UNSPECIFIED      ListRequest_Filter = 0\n\tListRequest_INTERCEPTS       ListRequest_Filter = 1\n\tListRequest_REPLACEMENTS     ListRequest_Filter = 2\n\tListRequest_INGESTS          ListRequest_Filter = 4\n\tListRequest_WIRETAPS         ListRequest_Filter = 8\n\tListRequest_INSTALLED_AGENTS ListRequest_Filter = 16\n\tListRequest_EVERYTHING       ListRequest_Filter = 31\n)\n\n// Enum value maps for ListRequest_Filter.\nvar (\n\tListRequest_Filter_name = map[int32]string{\n\t\t0:  \"UNSPECIFIED\",\n\t\t1:  \"INTERCEPTS\",\n\t\t2:  \"REPLACEMENTS\",\n\t\t4:  \"INGESTS\",\n\t\t8:  \"WIRETAPS\",\n\t\t16: \"INSTALLED_AGENTS\",\n\t\t31: \"EVERYTHING\",\n\t}\n\tListRequest_Filter_value = map[string]int32{\n\t\t\"UNSPECIFIED\":      0,\n\t\t\"INTERCEPTS\":       1,\n\t\t\"REPLACEMENTS\":     2,\n\t\t\"INGESTS\":          4,\n\t\t\"WIRETAPS\":         8,\n\t\t\"INSTALLED_AGENTS\": 16,\n\t\t\"EVERYTHING\":       31,\n\t}\n)\n\nfunc (x ListRequest_Filter) Enum() *ListRequest_Filter {\n\tp := new(ListRequest_Filter)\n\t*p = x\n\treturn p\n}\n\nfunc (x ListRequest_Filter) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (ListRequest_Filter) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_connector_connector_proto_enumTypes[1].Descriptor()\n}\n\nfunc (ListRequest_Filter) Type() protoreflect.EnumType {\n\treturn &file_connector_connector_proto_enumTypes[1]\n}\n\nfunc (x ListRequest_Filter) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use ListRequest_Filter.Descriptor instead.\nfunc (ListRequest_Filter) EnumDescriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{5, 0}\n}\n\ntype LogLevelRequest_Scope int32\n\nconst (\n\tLogLevelRequest_UNSPECIFIED LogLevelRequest_Scope = 0\n\tLogLevelRequest_LOCAL_ONLY  LogLevelRequest_Scope = 1 // applies only to the local daemon processes\n\tLogLevelRequest_REMOTE_ONLY LogLevelRequest_Scope = 2 // applies only to traffic-manager and traffic-agents\n)\n\n// Enum value maps for LogLevelRequest_Scope.\nvar (\n\tLogLevelRequest_Scope_name = map[int32]string{\n\t\t0: \"UNSPECIFIED\",\n\t\t1: \"LOCAL_ONLY\",\n\t\t2: \"REMOTE_ONLY\",\n\t}\n\tLogLevelRequest_Scope_value = map[string]int32{\n\t\t\"UNSPECIFIED\": 0,\n\t\t\"LOCAL_ONLY\":  1,\n\t\t\"REMOTE_ONLY\": 2,\n\t}\n)\n\nfunc (x LogLevelRequest_Scope) Enum() *LogLevelRequest_Scope {\n\tp := new(LogLevelRequest_Scope)\n\t*p = x\n\treturn p\n}\n\nfunc (x LogLevelRequest_Scope) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (LogLevelRequest_Scope) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_connector_connector_proto_enumTypes[2].Descriptor()\n}\n\nfunc (LogLevelRequest_Scope) Type() protoreflect.EnumType {\n\treturn &file_connector_connector_proto_enumTypes[2]\n}\n\nfunc (x LogLevelRequest_Scope) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use LogLevelRequest_Scope.Descriptor instead.\nfunc (LogLevelRequest_Scope) EnumDescriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{12, 0}\n}\n\ntype Interceptor struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The ID of the intercept that is served by this interceptor process\n\tInterceptId string `protobuf:\"bytes,1,opt,name=intercept_id,json=interceptId,proto3\" json:\"intercept_id,omitempty\"`\n\t// The pid of the interceptor process\n\tPid int32 `protobuf:\"varint,2,opt,name=pid,proto3\" json:\"pid,omitempty\"`\n\t// Name or ID of container, in case the intercept handler runs in Docker\n\tContainerName string `protobuf:\"bytes,3,opt,name=container_name,json=containerName,proto3\" json:\"container_name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Interceptor) Reset() {\n\t*x = Interceptor{}\n\tmi := &file_connector_connector_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Interceptor) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Interceptor) ProtoMessage() {}\n\nfunc (x *Interceptor) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Interceptor.ProtoReflect.Descriptor instead.\nfunc (*Interceptor) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Interceptor) GetInterceptId() string {\n\tif x != nil {\n\t\treturn x.InterceptId\n\t}\n\treturn \"\"\n}\n\nfunc (x *Interceptor) GetPid() int32 {\n\tif x != nil {\n\t\treturn x.Pid\n\t}\n\treturn 0\n}\n\nfunc (x *Interceptor) GetContainerName() string {\n\tif x != nil {\n\t\treturn x.ContainerName\n\t}\n\treturn \"\"\n}\n\n// ConnectRequest contains the information needed to connect ot a cluster.\ntype ConnectRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The kubernetes flags from the telepresence connect command\n\tKubeFlags               map[string]string           `protobuf:\"bytes,1,rep,name=kube_flags,json=kubeFlags,proto3\" json:\"kube_flags,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tMappedNamespaces        []string                    `protobuf:\"bytes,2,rep,name=mapped_namespaces,json=mappedNamespaces,proto3\" json:\"mapped_namespaces,omitempty\"`\n\tName                    string                      `protobuf:\"bytes,3,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tIsPodDaemon             bool                        `protobuf:\"varint,4,opt,name=is_pod_daemon,json=isPodDaemon,proto3\" json:\"is_pod_daemon,omitempty\"`\n\tAlsoProxy               []string                    `protobuf:\"bytes,5,rep,name=also_proxy,json=alsoProxy,proto3\" json:\"also_proxy,omitempty\"`    // protolint:disable:this REPEATED_FIELD_NAMES_PLURALIZED\n\tNeverProxy              []string                    `protobuf:\"bytes,6,rep,name=never_proxy,json=neverProxy,proto3\" json:\"never_proxy,omitempty\"` // protolint:disable:this REPEATED_FIELD_NAMES_PLURALIZED\n\tAllowConflictingSubnets []string                    `protobuf:\"bytes,10,rep,name=allow_conflicting_subnets,json=allowConflictingSubnets,proto3\" json:\"allow_conflicting_subnets,omitempty\"`\n\tSubnetViaWorkloads      []*daemon.SubnetViaWorkload `protobuf:\"bytes,11,rep,name=subnet_via_workloads,json=subnetViaWorkloads,proto3\" json:\"subnet_via_workloads,omitempty\"`\n\tManagerNamespace        string                      `protobuf:\"bytes,7,opt,name=manager_namespace,json=managerNamespace,proto3\" json:\"manager_namespace,omitempty\"`\n\tEnvironment             map[string]string           `protobuf:\"bytes,8,rep,name=environment,proto3\" json:\"environment,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// Kubeconfig YAML, if not to be loaded from file.\n\tKubeconfigData []byte `protobuf:\"bytes,12,opt,name=kubeconfig_data,json=kubeconfigData,proto3,oneof\" json:\"kubeconfig_data,omitempty\"`\n\tClientId       string `protobuf:\"bytes,13,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *ConnectRequest) Reset() {\n\t*x = ConnectRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ConnectRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ConnectRequest) ProtoMessage() {}\n\nfunc (x *ConnectRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ConnectRequest.ProtoReflect.Descriptor instead.\nfunc (*ConnectRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ConnectRequest) GetKubeFlags() map[string]string {\n\tif x != nil {\n\t\treturn x.KubeFlags\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetMappedNamespaces() []string {\n\tif x != nil {\n\t\treturn x.MappedNamespaces\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectRequest) GetIsPodDaemon() bool {\n\tif x != nil {\n\t\treturn x.IsPodDaemon\n\t}\n\treturn false\n}\n\nfunc (x *ConnectRequest) GetAlsoProxy() []string {\n\tif x != nil {\n\t\treturn x.AlsoProxy\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetNeverProxy() []string {\n\tif x != nil {\n\t\treturn x.NeverProxy\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetAllowConflictingSubnets() []string {\n\tif x != nil {\n\t\treturn x.AllowConflictingSubnets\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetSubnetViaWorkloads() []*daemon.SubnetViaWorkload {\n\tif x != nil {\n\t\treturn x.SubnetViaWorkloads\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetManagerNamespace() string {\n\tif x != nil {\n\t\treturn x.ManagerNamespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectRequest) GetEnvironment() map[string]string {\n\tif x != nil {\n\t\treturn x.Environment\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetKubeconfigData() []byte {\n\tif x != nil {\n\t\treturn x.KubeconfigData\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\ntype ConnectInfo struct {\n\tstate          protoimpl.MessageState `protogen:\"open.v1\"`\n\tClusterServer  string                 `protobuf:\"bytes,3,opt,name=cluster_server,json=clusterServer,proto3\" json:\"cluster_server,omitempty\"`\n\tClusterContext string                 `protobuf:\"bytes,4,opt,name=cluster_context,json=clusterContext,proto3\" json:\"cluster_context,omitempty\"`\n\tVersion        *common.VersionInfo    `protobuf:\"bytes,5,opt,name=version,proto3\" json:\"version,omitempty\"`\n\t// The name of the connection\n\tConnectionName string `protobuf:\"bytes,16,opt,name=connection_name,json=connectionName,proto3\" json:\"connection_name,omitempty\"`\n\t// The kubernetes flags from the telepresence connect command when the connection was established\n\tKubeFlags map[string]string `protobuf:\"bytes,17,rep,name=kube_flags,json=kubeFlags,proto3\" json:\"kube_flags,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// the namespace that the connector is connected to.\n\tNamespace          string                         `protobuf:\"bytes,6,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tManagerInstallId   string                         `protobuf:\"bytes,7,opt,name=manager_install_id,json=managerInstallId,proto3\" json:\"manager_install_id,omitempty\"`\n\tIntercepts         *manager.InterceptInfoSnapshot `protobuf:\"bytes,8,opt,name=intercepts,proto3\" json:\"intercepts,omitempty\"`\n\tIngests            []*IngestInfo                  `protobuf:\"bytes,9,rep,name=ingests,proto3\" json:\"ingests,omitempty\"`\n\tSessionInfo        *manager.SessionInfo           `protobuf:\"bytes,10,opt,name=session_info,json=sessionInfo,proto3\" json:\"session_info,omitempty\"`\n\tManagerVersion     *manager.VersionInfo2          `protobuf:\"bytes,19,opt,name=manager_version,json=managerVersion,proto3\" json:\"manager_version,omitempty\"`\n\tDaemonStatus       *daemon.DaemonStatus           `protobuf:\"bytes,13,opt,name=daemon_status,json=daemonStatus,proto3\" json:\"daemon_status,omitempty\"`\n\tManagerNamespace   string                         `protobuf:\"bytes,14,opt,name=manager_namespace,json=managerNamespace,proto3\" json:\"manager_namespace,omitempty\"`\n\tMappedNamespaces   []string                       `protobuf:\"bytes,15,rep,name=mapped_namespaces,json=mappedNamespaces,proto3\" json:\"mapped_namespaces,omitempty\"`\n\tSubnetViaWorkloads []*daemon.SubnetViaWorkload    `protobuf:\"bytes,18,rep,name=subnet_via_workloads,json=subnetViaWorkloads,proto3\" json:\"subnet_via_workloads,omitempty\"`\n\t// Connection was established by the caller's connect call.\n\tInitial       bool `protobuf:\"varint,20,opt,name=initial,proto3\" json:\"initial,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ConnectInfo) Reset() {\n\t*x = ConnectInfo{}\n\tmi := &file_connector_connector_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ConnectInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ConnectInfo) ProtoMessage() {}\n\nfunc (x *ConnectInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ConnectInfo.ProtoReflect.Descriptor instead.\nfunc (*ConnectInfo) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *ConnectInfo) GetClusterServer() string {\n\tif x != nil {\n\t\treturn x.ClusterServer\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectInfo) GetClusterContext() string {\n\tif x != nil {\n\t\treturn x.ClusterContext\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectInfo) GetVersion() *common.VersionInfo {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetConnectionName() string {\n\tif x != nil {\n\t\treturn x.ConnectionName\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectInfo) GetKubeFlags() map[string]string {\n\tif x != nil {\n\t\treturn x.KubeFlags\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectInfo) GetManagerInstallId() string {\n\tif x != nil {\n\t\treturn x.ManagerInstallId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectInfo) GetIntercepts() *manager.InterceptInfoSnapshot {\n\tif x != nil {\n\t\treturn x.Intercepts\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetIngests() []*IngestInfo {\n\tif x != nil {\n\t\treturn x.Ingests\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetSessionInfo() *manager.SessionInfo {\n\tif x != nil {\n\t\treturn x.SessionInfo\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetManagerVersion() *manager.VersionInfo2 {\n\tif x != nil {\n\t\treturn x.ManagerVersion\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetDaemonStatus() *daemon.DaemonStatus {\n\tif x != nil {\n\t\treturn x.DaemonStatus\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetManagerNamespace() string {\n\tif x != nil {\n\t\treturn x.ManagerNamespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *ConnectInfo) GetMappedNamespaces() []string {\n\tif x != nil {\n\t\treturn x.MappedNamespaces\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetSubnetViaWorkloads() []*daemon.SubnetViaWorkload {\n\tif x != nil {\n\t\treturn x.SubnetViaWorkloads\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectInfo) GetInitial() bool {\n\tif x != nil {\n\t\treturn x.Initial\n\t}\n\treturn false\n}\n\ntype UninstallRequest struct {\n\tstate         protoimpl.MessageState         `protogen:\"open.v1\"`\n\tUninstallType UninstallRequest_UninstallType `protobuf:\"varint,1,opt,name=uninstall_type,json=uninstallType,proto3,enum=telepresence.connector.UninstallRequest_UninstallType\" json:\"uninstall_type,omitempty\"`\n\tAgents        []string                       `protobuf:\"bytes,2,rep,name=agents,proto3\" json:\"agents,omitempty\"`\n\t// Namespace of agents to remove.\n\tNamespace     string `protobuf:\"bytes,3,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UninstallRequest) Reset() {\n\t*x = UninstallRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UninstallRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UninstallRequest) ProtoMessage() {}\n\nfunc (x *UninstallRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UninstallRequest.ProtoReflect.Descriptor instead.\nfunc (*UninstallRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *UninstallRequest) GetUninstallType() UninstallRequest_UninstallType {\n\tif x != nil {\n\t\treturn x.UninstallType\n\t}\n\treturn UninstallRequest_UNSPECIFIED\n}\n\nfunc (x *UninstallRequest) GetAgents() []string {\n\tif x != nil {\n\t\treturn x.Agents\n\t}\n\treturn nil\n}\n\nfunc (x *UninstallRequest) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\ntype CreateInterceptRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// No need to set spec.client; the connector will fill that in for\n\t// you.\n\tSpec           *manager.InterceptSpec `protobuf:\"bytes,1,opt,name=spec,proto3\" json:\"spec,omitempty\"`\n\tMountPoint     string                 `protobuf:\"bytes,2,opt,name=mount_point,json=mountPoint,proto3\" json:\"mount_point,omitempty\"`\n\tAgentImage     string                 `protobuf:\"bytes,3,opt,name=agent_image,json=agentImage,proto3\" json:\"agent_image,omitempty\"`\n\tIsPodDaemon    bool                   `protobuf:\"varint,4,opt,name=is_pod_daemon,json=isPodDaemon,proto3\" json:\"is_pod_daemon,omitempty\"`\n\tExtendedInfo   []byte                 `protobuf:\"bytes,5,opt,name=extended_info,json=extendedInfo,proto3\" json:\"extended_info,omitempty\"`\n\tLocalMountPort int32                  `protobuf:\"varint,6,opt,name=local_mount_port,json=localMountPort,proto3\" json:\"local_mount_port,omitempty\"`\n\tMountReadOnly  bool                   `protobuf:\"varint,7,opt,name=mount_read_only,json=mountReadOnly,proto3\" json:\"mount_read_only,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *CreateInterceptRequest) Reset() {\n\t*x = CreateInterceptRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateInterceptRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateInterceptRequest) ProtoMessage() {}\n\nfunc (x *CreateInterceptRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateInterceptRequest.ProtoReflect.Descriptor instead.\nfunc (*CreateInterceptRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *CreateInterceptRequest) GetSpec() *manager.InterceptSpec {\n\tif x != nil {\n\t\treturn x.Spec\n\t}\n\treturn nil\n}\n\nfunc (x *CreateInterceptRequest) GetMountPoint() string {\n\tif x != nil {\n\t\treturn x.MountPoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreateInterceptRequest) GetAgentImage() string {\n\tif x != nil {\n\t\treturn x.AgentImage\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreateInterceptRequest) GetIsPodDaemon() bool {\n\tif x != nil {\n\t\treturn x.IsPodDaemon\n\t}\n\treturn false\n}\n\nfunc (x *CreateInterceptRequest) GetExtendedInfo() []byte {\n\tif x != nil {\n\t\treturn x.ExtendedInfo\n\t}\n\treturn nil\n}\n\nfunc (x *CreateInterceptRequest) GetLocalMountPort() int32 {\n\tif x != nil {\n\t\treturn x.LocalMountPort\n\t}\n\treturn 0\n}\n\nfunc (x *CreateInterceptRequest) GetMountReadOnly() bool {\n\tif x != nil {\n\t\treturn x.MountReadOnly\n\t}\n\treturn false\n}\n\ntype ListRequest struct {\n\tstate  protoimpl.MessageState `protogen:\"open.v1\"`\n\tFilter ListRequest_Filter     `protobuf:\"varint,1,opt,name=filter,proto3,enum=telepresence.connector.ListRequest_Filter\" json:\"filter,omitempty\"`\n\t// Namespace to list.\n\tNamespace     string `protobuf:\"bytes,2,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListRequest) Reset() {\n\t*x = ListRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRequest) ProtoMessage() {}\n\nfunc (x *ListRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRequest.ProtoReflect.Descriptor instead.\nfunc (*ListRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ListRequest) GetFilter() ListRequest_Filter {\n\tif x != nil {\n\t\treturn x.Filter\n\t}\n\treturn ListRequest_UNSPECIFIED\n}\n\nfunc (x *ListRequest) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\ntype IngestIdentifier struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Name of the workload that holds the desire container.\n\tWorkloadName string `protobuf:\"bytes,1,opt,name=workload_name,json=workloadName,proto3\" json:\"workload_name,omitempty\"`\n\t// The name of the desired container. Must be set when the workload contains more\n\t// than one container candidate.\n\tContainerName string `protobuf:\"bytes,2,opt,name=container_name,json=containerName,proto3\" json:\"container_name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *IngestIdentifier) Reset() {\n\t*x = IngestIdentifier{}\n\tmi := &file_connector_connector_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IngestIdentifier) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IngestIdentifier) ProtoMessage() {}\n\nfunc (x *IngestIdentifier) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use IngestIdentifier.ProtoReflect.Descriptor instead.\nfunc (*IngestIdentifier) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *IngestIdentifier) GetWorkloadName() string {\n\tif x != nil {\n\t\treturn x.WorkloadName\n\t}\n\treturn \"\"\n}\n\nfunc (x *IngestIdentifier) GetContainerName() string {\n\tif x != nil {\n\t\treturn x.ContainerName\n\t}\n\treturn \"\"\n}\n\ntype IngestRequest struct {\n\tstate      protoimpl.MessageState `protogen:\"open.v1\"`\n\tIdentifier *IngestIdentifier      `protobuf:\"bytes,1,opt,name=identifier,proto3\" json:\"identifier,omitempty\"`\n\t// Desired mount point. Can be set to \"true\" to generated temporary mount point,\n\t// \"false\" to prevent that mounting takes place, an explicit path to use for the\n\t// mount or an empty string in combination with a non-zero local_mount_port.\n\tMountPoint string `protobuf:\"bytes,2,opt,name=mount_point,json=mountPoint,proto3\" json:\"mount_point,omitempty\"`\n\t// Local port where an sftp client can connect when doing docker volume mounts.\n\tLocalMountPort int32 `protobuf:\"varint,3,opt,name=local_mount_port,json=localMountPort,proto3\" json:\"local_mount_port,omitempty\"`\n\t// Extra ports that will be forwarded from the intercepting client's localhost\n\t// to the intercepted pod. Each entry is a string containing a port number followed\n\t// by an optional \"/TCP\" or \"/UDP\".\n\tLocalPorts    []string `protobuf:\"bytes,4,rep,name=local_ports,json=localPorts,proto3\" json:\"local_ports,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *IngestRequest) Reset() {\n\t*x = IngestRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IngestRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IngestRequest) ProtoMessage() {}\n\nfunc (x *IngestRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use IngestRequest.ProtoReflect.Descriptor instead.\nfunc (*IngestRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *IngestRequest) GetIdentifier() *IngestIdentifier {\n\tif x != nil {\n\t\treturn x.Identifier\n\t}\n\treturn nil\n}\n\nfunc (x *IngestRequest) GetMountPoint() string {\n\tif x != nil {\n\t\treturn x.MountPoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *IngestRequest) GetLocalMountPort() int32 {\n\tif x != nil {\n\t\treturn x.LocalMountPort\n\t}\n\treturn 0\n}\n\nfunc (x *IngestRequest) GetLocalPorts() []string {\n\tif x != nil {\n\t\treturn x.LocalPorts\n\t}\n\treturn nil\n}\n\ntype IngestInfo struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Name of ingested workload\n\tWorkload     string `protobuf:\"bytes,1,opt,name=workload,proto3\" json:\"workload,omitempty\"`\n\tWorkloadKind string `protobuf:\"bytes,2,opt,name=workload_kind,json=workloadKind,proto3\" json:\"workload_kind,omitempty\"`\n\t// Name of ingested container\n\tContainer string `protobuf:\"bytes,3,opt,name=container,proto3\" json:\"container,omitempty\"`\n\t// The directory where the intercept mounts can be found in the agent.\n\tMountPoint string `protobuf:\"bytes,4,opt,name=mount_point,json=mountPoint,proto3\" json:\"mount_point,omitempty\"`\n\t// The IP of the ingested pod.\n\tPodIp string `protobuf:\"bytes,5,opt,name=pod_ip,json=podIp,proto3\" json:\"pod_ip,omitempty\"`\n\t// The port where the SFTP server listens.\n\tSftpPort int32 `protobuf:\"varint,6,opt,name=sftp_port,json=sftpPort,proto3\" json:\"sftp_port,omitempty\"`\n\t// The port where the FTP server listens.\n\tFtpPort int32 `protobuf:\"varint,7,opt,name=ftp_port,json=ftpPort,proto3\" json:\"ftp_port,omitempty\"`\n\t// The environment of the ingested container\n\tEnvironment map[string]string `protobuf:\"bytes,8,rep,name=environment,proto3\" json:\"environment,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// The directory where the client mounts the remote mount_point.\n\tClientMountPoint string `protobuf:\"bytes,9,opt,name=client_mount_point,json=clientMountPoint,proto3\" json:\"client_mount_point,omitempty\"`\n\t// Map of mount path -> MountPolicy\n\tMounts        map[string]int32 `protobuf:\"bytes,10,rep,name=mounts,proto3\" json:\"mounts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"varint,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *IngestInfo) Reset() {\n\t*x = IngestInfo{}\n\tmi := &file_connector_connector_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IngestInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IngestInfo) ProtoMessage() {}\n\nfunc (x *IngestInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use IngestInfo.ProtoReflect.Descriptor instead.\nfunc (*IngestInfo) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *IngestInfo) GetWorkload() string {\n\tif x != nil {\n\t\treturn x.Workload\n\t}\n\treturn \"\"\n}\n\nfunc (x *IngestInfo) GetWorkloadKind() string {\n\tif x != nil {\n\t\treturn x.WorkloadKind\n\t}\n\treturn \"\"\n}\n\nfunc (x *IngestInfo) GetContainer() string {\n\tif x != nil {\n\t\treturn x.Container\n\t}\n\treturn \"\"\n}\n\nfunc (x *IngestInfo) GetMountPoint() string {\n\tif x != nil {\n\t\treturn x.MountPoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *IngestInfo) GetPodIp() string {\n\tif x != nil {\n\t\treturn x.PodIp\n\t}\n\treturn \"\"\n}\n\nfunc (x *IngestInfo) GetSftpPort() int32 {\n\tif x != nil {\n\t\treturn x.SftpPort\n\t}\n\treturn 0\n}\n\nfunc (x *IngestInfo) GetFtpPort() int32 {\n\tif x != nil {\n\t\treturn x.FtpPort\n\t}\n\treturn 0\n}\n\nfunc (x *IngestInfo) GetEnvironment() map[string]string {\n\tif x != nil {\n\t\treturn x.Environment\n\t}\n\treturn nil\n}\n\nfunc (x *IngestInfo) GetClientMountPoint() string {\n\tif x != nil {\n\t\treturn x.ClientMountPoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *IngestInfo) GetMounts() map[string]int32 {\n\tif x != nil {\n\t\treturn x.Mounts\n\t}\n\treturn nil\n}\n\ntype WatchWorkloadsRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Namespace to watch.\n\tNamespaces    []string `protobuf:\"bytes,1,rep,name=namespaces,proto3\" json:\"namespaces,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WatchWorkloadsRequest) Reset() {\n\t*x = WatchWorkloadsRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WatchWorkloadsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WatchWorkloadsRequest) ProtoMessage() {}\n\nfunc (x *WatchWorkloadsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WatchWorkloadsRequest.ProtoReflect.Descriptor instead.\nfunc (*WatchWorkloadsRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *WatchWorkloadsRequest) GetNamespaces() []string {\n\tif x != nil {\n\t\treturn x.Namespaces\n\t}\n\treturn nil\n}\n\n// WorkloadInfo contains information about a workload\n// https://kubernetes.io/docs/concepts/workloads/\ntype WorkloadInfo struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Name of workload\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Namespace of workload\n\tNamespace string `protobuf:\"bytes,2,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\t// Reason why workload cannot be intercepted, or empty if it can.\n\tNotInterceptableReason string `protobuf:\"bytes,3,opt,name=not_interceptable_reason,json=notInterceptableReason,proto3\" json:\"not_interceptable_reason,omitempty\"`\n\t// InterceptInfo reported from the traffic manager in case the workload is currently intercepted\n\tInterceptInfo []*manager.InterceptInfo `protobuf:\"bytes,4,rep,name=intercept_info,json=interceptInfo,proto3\" json:\"intercept_info,omitempty\"`\n\t// IngestInfo reported from the traffic manager in case the workload is currently intercepted\n\tIngestInfo []*IngestInfo `protobuf:\"bytes,5,rep,name=ingest_info,json=ingestInfo,proto3\" json:\"ingest_info,omitempty\"`\n\t// Workload Resource type (e.g. Deployment, ReplicaSet, StatefulSet, Rollout)\n\tWorkloadResourceType string `protobuf:\"bytes,6,opt,name=workload_resource_type,json=workloadResourceType,proto3\" json:\"workload_resource_type,omitempty\"`\n\tUid                  string `protobuf:\"bytes,7,opt,name=uid,proto3\" json:\"uid,omitempty\"`\n\tAgentVersion         string `protobuf:\"bytes,8,opt,name=agent_version,json=agentVersion,proto3\" json:\"agent_version,omitempty\"`\n\tunknownFields        protoimpl.UnknownFields\n\tsizeCache            protoimpl.SizeCache\n}\n\nfunc (x *WorkloadInfo) Reset() {\n\t*x = WorkloadInfo{}\n\tmi := &file_connector_connector_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WorkloadInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WorkloadInfo) ProtoMessage() {}\n\nfunc (x *WorkloadInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WorkloadInfo.ProtoReflect.Descriptor instead.\nfunc (*WorkloadInfo) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *WorkloadInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *WorkloadInfo) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *WorkloadInfo) GetNotInterceptableReason() string {\n\tif x != nil {\n\t\treturn x.NotInterceptableReason\n\t}\n\treturn \"\"\n}\n\nfunc (x *WorkloadInfo) GetInterceptInfo() []*manager.InterceptInfo {\n\tif x != nil {\n\t\treturn x.InterceptInfo\n\t}\n\treturn nil\n}\n\nfunc (x *WorkloadInfo) GetIngestInfo() []*IngestInfo {\n\tif x != nil {\n\t\treturn x.IngestInfo\n\t}\n\treturn nil\n}\n\nfunc (x *WorkloadInfo) GetWorkloadResourceType() string {\n\tif x != nil {\n\t\treturn x.WorkloadResourceType\n\t}\n\treturn \"\"\n}\n\nfunc (x *WorkloadInfo) GetUid() string {\n\tif x != nil {\n\t\treturn x.Uid\n\t}\n\treturn \"\"\n}\n\nfunc (x *WorkloadInfo) GetAgentVersion() string {\n\tif x != nil {\n\t\treturn x.AgentVersion\n\t}\n\treturn \"\"\n}\n\ntype WorkloadInfoSnapshot struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tWorkloads     []*WorkloadInfo        `protobuf:\"bytes,1,rep,name=workloads,proto3\" json:\"workloads,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WorkloadInfoSnapshot) Reset() {\n\t*x = WorkloadInfoSnapshot{}\n\tmi := &file_connector_connector_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WorkloadInfoSnapshot) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WorkloadInfoSnapshot) ProtoMessage() {}\n\nfunc (x *WorkloadInfoSnapshot) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WorkloadInfoSnapshot.ProtoReflect.Descriptor instead.\nfunc (*WorkloadInfoSnapshot) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *WorkloadInfoSnapshot) GetWorkloads() []*WorkloadInfo {\n\tif x != nil {\n\t\treturn x.Workloads\n\t}\n\treturn nil\n}\n\ntype LogLevelRequest struct {\n\tstate    protoimpl.MessageState `protogen:\"open.v1\"`\n\tLogLevel string                 `protobuf:\"bytes,1,opt,name=log_level,json=logLevel,proto3\" json:\"log_level,omitempty\"`\n\t// The time that this log-level will be in effect before\n\t// falling back to the configured log-level.\n\tDuration      *durationpb.Duration  `protobuf:\"bytes,2,opt,name=duration,proto3\" json:\"duration,omitempty\"`\n\tScope         LogLevelRequest_Scope `protobuf:\"varint,3,opt,name=scope,proto3,enum=telepresence.connector.LogLevelRequest_Scope\" json:\"scope,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LogLevelRequest) Reset() {\n\t*x = LogLevelRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LogLevelRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LogLevelRequest) ProtoMessage() {}\n\nfunc (x *LogLevelRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LogLevelRequest.ProtoReflect.Descriptor instead.\nfunc (*LogLevelRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *LogLevelRequest) GetLogLevel() string {\n\tif x != nil {\n\t\treturn x.LogLevel\n\t}\n\treturn \"\"\n}\n\nfunc (x *LogLevelRequest) GetDuration() *durationpb.Duration {\n\tif x != nil {\n\t\treturn x.Duration\n\t}\n\treturn nil\n}\n\nfunc (x *LogLevelRequest) GetScope() LogLevelRequest_Scope {\n\tif x != nil {\n\t\treturn x.Scope\n\t}\n\treturn LogLevelRequest_UNSPECIFIED\n}\n\ntype LogsRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Whether or not logs from the traffic-manager are desired.\n\tTrafficManager bool `protobuf:\"varint,1,opt,name=traffic_manager,json=trafficManager,proto3\" json:\"traffic_manager,omitempty\"`\n\t// Whether or not to get the pod yaml deployed to the cluster.\n\tGetPodYaml bool `protobuf:\"varint,2,opt,name=get_pod_yaml,json=getPodYaml,proto3\" json:\"get_pod_yaml,omitempty\"`\n\t// The traffic-agent(s) logs are desired from. Can be `all`, `False`,\n\t// or substring to filter based on pod names.\n\tAgents string `protobuf:\"bytes,3,opt,name=agents,proto3\" json:\"agents,omitempty\"`\n\t// Directory that the logs will be exported to\n\tExportDir     string `protobuf:\"bytes,4,opt,name=export_dir,json=exportDir,proto3\" json:\"export_dir,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LogsRequest) Reset() {\n\t*x = LogsRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LogsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LogsRequest) ProtoMessage() {}\n\nfunc (x *LogsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[13]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LogsRequest.ProtoReflect.Descriptor instead.\nfunc (*LogsRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *LogsRequest) GetTrafficManager() bool {\n\tif x != nil {\n\t\treturn x.TrafficManager\n\t}\n\treturn false\n}\n\nfunc (x *LogsRequest) GetGetPodYaml() bool {\n\tif x != nil {\n\t\treturn x.GetPodYaml\n\t}\n\treturn false\n}\n\nfunc (x *LogsRequest) GetAgents() string {\n\tif x != nil {\n\t\treturn x.Agents\n\t}\n\treturn \"\"\n}\n\nfunc (x *LogsRequest) GetExportDir() string {\n\tif x != nil {\n\t\treturn x.ExportDir\n\t}\n\treturn \"\"\n}\n\ntype LogsResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// General error that isn't associated with a pod such as failing to list the pods.\n\tError string `protobuf:\"bytes,1,opt,name=error,proto3\" json:\"error,omitempty\"`\n\t// pod_info contains one entry per created file name name. The value is either the string\n\t// \"ok\" indicating that the file exists, or an error string with info why it could not\n\t// be created.\n\tPodInfo       map[string]string `protobuf:\"bytes,2,rep,name=pod_info,json=podInfo,proto3\" json:\"pod_info,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LogsResponse) Reset() {\n\t*x = LogsResponse{}\n\tmi := &file_connector_connector_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LogsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LogsResponse) ProtoMessage() {}\n\nfunc (x *LogsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[14]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LogsResponse.ProtoReflect.Descriptor instead.\nfunc (*LogsResponse) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *LogsResponse) GetError() string {\n\tif x != nil {\n\t\treturn x.Error\n\t}\n\treturn \"\"\n}\n\nfunc (x *LogsResponse) GetPodInfo() map[string]string {\n\tif x != nil {\n\t\treturn x.PodInfo\n\t}\n\treturn nil\n}\n\ntype GetNamespacesRequest struct {\n\tstate           protoimpl.MessageState `protogen:\"open.v1\"`\n\tForClientAccess bool                   `protobuf:\"varint,1,opt,name=for_client_access,json=forClientAccess,proto3\" json:\"for_client_access,omitempty\"`\n\tPrefix          string                 `protobuf:\"bytes,2,opt,name=prefix,proto3\" json:\"prefix,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *GetNamespacesRequest) Reset() {\n\t*x = GetNamespacesRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetNamespacesRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetNamespacesRequest) ProtoMessage() {}\n\nfunc (x *GetNamespacesRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[15]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetNamespacesRequest.ProtoReflect.Descriptor instead.\nfunc (*GetNamespacesRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *GetNamespacesRequest) GetForClientAccess() bool {\n\tif x != nil {\n\t\treturn x.ForClientAccess\n\t}\n\treturn false\n}\n\nfunc (x *GetNamespacesRequest) GetPrefix() string {\n\tif x != nil {\n\t\treturn x.Prefix\n\t}\n\treturn \"\"\n}\n\ntype GetNamespacesResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNamespaces    []string               `protobuf:\"bytes,2,rep,name=namespaces,proto3\" json:\"namespaces,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetNamespacesResponse) Reset() {\n\t*x = GetNamespacesResponse{}\n\tmi := &file_connector_connector_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetNamespacesResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetNamespacesResponse) ProtoMessage() {}\n\nfunc (x *GetNamespacesResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[16]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetNamespacesResponse.ProtoReflect.Descriptor instead.\nfunc (*GetNamespacesResponse) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *GetNamespacesResponse) GetNamespaces() []string {\n\tif x != nil {\n\t\treturn x.Namespaces\n\t}\n\treturn nil\n}\n\ntype ClientConfig struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tJson          []byte                 `protobuf:\"bytes,1,opt,name=json,proto3\" json:\"json,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClientConfig) Reset() {\n\t*x = ClientConfig{}\n\tmi := &file_connector_connector_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientConfig) ProtoMessage() {}\n\nfunc (x *ClientConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[17]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.\nfunc (*ClientConfig) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *ClientConfig) GetJson() []byte {\n\tif x != nil {\n\t\treturn x.Json\n\t}\n\treturn nil\n}\n\n// ClusterSubnets are the cluster subnets that the daemon has detected that need to be\n// routed\ntype ClusterSubnets struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// pod_subnets are the subnets that pods go into\n\tPodSubnets []*manager.IPNet `protobuf:\"bytes,1,rep,name=pod_subnets,json=podSubnets,proto3\" json:\"pod_subnets,omitempty\"`\n\t// svc_subnets are subnets that services go into\n\tSvcSubnets    []*manager.IPNet `protobuf:\"bytes,2,rep,name=svc_subnets,json=svcSubnets,proto3\" json:\"svc_subnets,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClusterSubnets) Reset() {\n\t*x = ClusterSubnets{}\n\tmi := &file_connector_connector_proto_msgTypes[18]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClusterSubnets) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClusterSubnets) ProtoMessage() {}\n\nfunc (x *ClusterSubnets) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[18]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClusterSubnets.ProtoReflect.Descriptor instead.\nfunc (*ClusterSubnets) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *ClusterSubnets) GetPodSubnets() []*manager.IPNet {\n\tif x != nil {\n\t\treturn x.PodSubnets\n\t}\n\treturn nil\n}\n\nfunc (x *ClusterSubnets) GetSvcSubnets() []*manager.IPNet {\n\tif x != nil {\n\t\treturn x.SvcSubnets\n\t}\n\treturn nil\n}\n\ntype ResolveSyntheticRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The synthetic IP to be resolved.\n\tIp []byte `protobuf:\"bytes,1,opt,name=ip,proto3\" json:\"ip,omitempty\"`\n\t// Resolve the IP into a name, but do not make an attempt to resolve the name through DNS.\n\tNameOnly      bool `protobuf:\"varint,2,opt,name=name_only,json=nameOnly,proto3\" json:\"name_only,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ResolveSyntheticRequest) Reset() {\n\t*x = ResolveSyntheticRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[19]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ResolveSyntheticRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ResolveSyntheticRequest) ProtoMessage() {}\n\nfunc (x *ResolveSyntheticRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[19]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ResolveSyntheticRequest.ProtoReflect.Descriptor instead.\nfunc (*ResolveSyntheticRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *ResolveSyntheticRequest) GetIp() []byte {\n\tif x != nil {\n\t\treturn x.Ip\n\t}\n\treturn nil\n}\n\nfunc (x *ResolveSyntheticRequest) GetNameOnly() bool {\n\tif x != nil {\n\t\treturn x.NameOnly\n\t}\n\treturn false\n}\n\ntype ResolveSyntheticResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The name that the synthetic IP resolved to.\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// The IP resulting from a DNS lookup using the resolved name.\n\tResolvedIp    []byte `protobuf:\"bytes,2,opt,name=resolved_ip,json=resolvedIp,proto3\" json:\"resolved_ip,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ResolveSyntheticResponse) Reset() {\n\t*x = ResolveSyntheticResponse{}\n\tmi := &file_connector_connector_proto_msgTypes[20]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ResolveSyntheticResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ResolveSyntheticResponse) ProtoMessage() {}\n\nfunc (x *ResolveSyntheticResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[20]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ResolveSyntheticResponse.ProtoReflect.Descriptor instead.\nfunc (*ResolveSyntheticResponse) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *ResolveSyntheticResponse) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *ResolveSyntheticResponse) GetResolvedIp() []byte {\n\tif x != nil {\n\t\treturn x.ResolvedIp\n\t}\n\treturn nil\n}\n\ntype RevokeInterceptRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The full intercept ID in the format \"sessionID:interceptName\"\n\t// This allows revoking intercepts for any client, not just the caller's own intercepts\n\tInterceptId   string `protobuf:\"bytes,1,opt,name=intercept_id,json=interceptId,proto3\" json:\"intercept_id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RevokeInterceptRequest) Reset() {\n\t*x = RevokeInterceptRequest{}\n\tmi := &file_connector_connector_proto_msgTypes[21]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RevokeInterceptRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RevokeInterceptRequest) ProtoMessage() {}\n\nfunc (x *RevokeInterceptRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_connector_connector_proto_msgTypes[21]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RevokeInterceptRequest.ProtoReflect.Descriptor instead.\nfunc (*RevokeInterceptRequest) Descriptor() ([]byte, []int) {\n\treturn file_connector_connector_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *RevokeInterceptRequest) GetInterceptId() string {\n\tif x != nil {\n\t\treturn x.InterceptId\n\t}\n\treturn \"\"\n}\n\nvar File_connector_connector_proto protoreflect.FileDescriptor\n\nconst file_connector_connector_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x19connector/connector.proto\\x12\\x16telepresence.connector\\x1a\\x14common/version.proto\\x1a\\x13daemon/daemon.proto\\x1a\\x1egoogle/protobuf/duration.proto\\x1a\\x1bgoogle/protobuf/empty.proto\\x1a\\x15manager/manager.proto\\\"i\\n\" +\n\t\"\\vInterceptor\\x12!\\n\" +\n\t\"\\fintercept_id\\x18\\x01 \\x01(\\tR\\vinterceptId\\x12\\x10\\n\" +\n\t\"\\x03pid\\x18\\x02 \\x01(\\x05R\\x03pid\\x12%\\n\" +\n\t\"\\x0econtainer_name\\x18\\x03 \\x01(\\tR\\rcontainerName\\\"\\x86\\x06\\n\" +\n\t\"\\x0eConnectRequest\\x12T\\n\" +\n\t\"\\n\" +\n\t\"kube_flags\\x18\\x01 \\x03(\\v25.telepresence.connector.ConnectRequest.KubeFlagsEntryR\\tkubeFlags\\x12+\\n\" +\n\t\"\\x11mapped_namespaces\\x18\\x02 \\x03(\\tR\\x10mappedNamespaces\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x03 \\x01(\\tR\\x04name\\x12\\\"\\n\" +\n\t\"\\ris_pod_daemon\\x18\\x04 \\x01(\\bR\\visPodDaemon\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"also_proxy\\x18\\x05 \\x03(\\tR\\talsoProxy\\x12\\x1f\\n\" +\n\t\"\\vnever_proxy\\x18\\x06 \\x03(\\tR\\n\" +\n\t\"neverProxy\\x12:\\n\" +\n\t\"\\x19allow_conflicting_subnets\\x18\\n\" +\n\t\" \\x03(\\tR\\x17allowConflictingSubnets\\x12X\\n\" +\n\t\"\\x14subnet_via_workloads\\x18\\v \\x03(\\v2&.telepresence.daemon.SubnetViaWorkloadR\\x12subnetViaWorkloads\\x12+\\n\" +\n\t\"\\x11manager_namespace\\x18\\a \\x01(\\tR\\x10managerNamespace\\x12Y\\n\" +\n\t\"\\venvironment\\x18\\b \\x03(\\v27.telepresence.connector.ConnectRequest.EnvironmentEntryR\\venvironment\\x12,\\n\" +\n\t\"\\x0fkubeconfig_data\\x18\\f \\x01(\\fH\\x00R\\x0ekubeconfigData\\x88\\x01\\x01\\x12\\x1b\\n\" +\n\t\"\\tclient_id\\x18\\r \\x01(\\tR\\bclientId\\x1a<\\n\" +\n\t\"\\x0eKubeFlagsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\x1a>\\n\" +\n\t\"\\x10EnvironmentEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01B\\x12\\n\" +\n\t\"\\x10_kubeconfig_data\\\"\\xeb\\a\\n\" +\n\t\"\\vConnectInfo\\x12%\\n\" +\n\t\"\\x0ecluster_server\\x18\\x03 \\x01(\\tR\\rclusterServer\\x12'\\n\" +\n\t\"\\x0fcluster_context\\x18\\x04 \\x01(\\tR\\x0eclusterContext\\x12:\\n\" +\n\t\"\\aversion\\x18\\x05 \\x01(\\v2 .telepresence.common.VersionInfoR\\aversion\\x12'\\n\" +\n\t\"\\x0fconnection_name\\x18\\x10 \\x01(\\tR\\x0econnectionName\\x12Q\\n\" +\n\t\"\\n\" +\n\t\"kube_flags\\x18\\x11 \\x03(\\v22.telepresence.connector.ConnectInfo.KubeFlagsEntryR\\tkubeFlags\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x06 \\x01(\\tR\\tnamespace\\x12,\\n\" +\n\t\"\\x12manager_install_id\\x18\\a \\x01(\\tR\\x10managerInstallId\\x12K\\n\" +\n\t\"\\n\" +\n\t\"intercepts\\x18\\b \\x01(\\v2+.telepresence.manager.InterceptInfoSnapshotR\\n\" +\n\t\"intercepts\\x12<\\n\" +\n\t\"\\aingests\\x18\\t \\x03(\\v2\\\".telepresence.connector.IngestInfoR\\aingests\\x12D\\n\" +\n\t\"\\fsession_info\\x18\\n\" +\n\t\" \\x01(\\v2!.telepresence.manager.SessionInfoR\\vsessionInfo\\x12K\\n\" +\n\t\"\\x0fmanager_version\\x18\\x13 \\x01(\\v2\\\".telepresence.manager.VersionInfo2R\\x0emanagerVersion\\x12F\\n\" +\n\t\"\\rdaemon_status\\x18\\r \\x01(\\v2!.telepresence.daemon.DaemonStatusR\\fdaemonStatus\\x12+\\n\" +\n\t\"\\x11manager_namespace\\x18\\x0e \\x01(\\tR\\x10managerNamespace\\x12+\\n\" +\n\t\"\\x11mapped_namespaces\\x18\\x0f \\x03(\\tR\\x10mappedNamespaces\\x12X\\n\" +\n\t\"\\x14subnet_via_workloads\\x18\\x12 \\x03(\\v2&.telepresence.daemon.SubnetViaWorkloadR\\x12subnetViaWorkloads\\x12\\x18\\n\" +\n\t\"\\ainitial\\x18\\x14 \\x01(\\bR\\ainitial\\x1a<\\n\" +\n\t\"\\x0eKubeFlagsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01J\\x04\\b\\x01\\x10\\x02J\\x04\\b\\x02\\x10\\x03J\\x04\\b\\v\\x10\\fJ\\x04\\b\\f\\x10\\r\\\"\\xeb\\x01\\n\" +\n\t\"\\x10UninstallRequest\\x12]\\n\" +\n\t\"\\x0euninstall_type\\x18\\x01 \\x01(\\x0e26.telepresence.connector.UninstallRequest.UninstallTypeR\\runinstallType\\x12\\x16\\n\" +\n\t\"\\x06agents\\x18\\x02 \\x03(\\tR\\x06agents\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x03 \\x01(\\tR\\tnamespace\\\"B\\n\" +\n\t\"\\rUninstallType\\x12\\x0f\\n\" +\n\t\"\\vUNSPECIFIED\\x10\\x00\\x12\\x10\\n\" +\n\t\"\\fNAMED_AGENTS\\x10\\x01\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"ALL_AGENTS\\x10\\x02\\\"\\xae\\x02\\n\" +\n\t\"\\x16CreateInterceptRequest\\x127\\n\" +\n\t\"\\x04spec\\x18\\x01 \\x01(\\v2#.telepresence.manager.InterceptSpecR\\x04spec\\x12\\x1f\\n\" +\n\t\"\\vmount_point\\x18\\x02 \\x01(\\tR\\n\" +\n\t\"mountPoint\\x12\\x1f\\n\" +\n\t\"\\vagent_image\\x18\\x03 \\x01(\\tR\\n\" +\n\t\"agentImage\\x12\\\"\\n\" +\n\t\"\\ris_pod_daemon\\x18\\x04 \\x01(\\bR\\visPodDaemon\\x12#\\n\" +\n\t\"\\rextended_info\\x18\\x05 \\x01(\\fR\\fextendedInfo\\x12(\\n\" +\n\t\"\\x10local_mount_port\\x18\\x06 \\x01(\\x05R\\x0elocalMountPort\\x12&\\n\" +\n\t\"\\x0fmount_read_only\\x18\\a \\x01(\\bR\\rmountReadOnly\\\"\\xed\\x01\\n\" +\n\t\"\\vListRequest\\x12B\\n\" +\n\t\"\\x06filter\\x18\\x01 \\x01(\\x0e2*.telepresence.connector.ListRequest.FilterR\\x06filter\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x02 \\x01(\\tR\\tnamespace\\\"|\\n\" +\n\t\"\\x06Filter\\x12\\x0f\\n\" +\n\t\"\\vUNSPECIFIED\\x10\\x00\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"INTERCEPTS\\x10\\x01\\x12\\x10\\n\" +\n\t\"\\fREPLACEMENTS\\x10\\x02\\x12\\v\\n\" +\n\t\"\\aINGESTS\\x10\\x04\\x12\\f\\n\" +\n\t\"\\bWIRETAPS\\x10\\b\\x12\\x14\\n\" +\n\t\"\\x10INSTALLED_AGENTS\\x10\\x10\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"EVERYTHING\\x10\\x1f\\\"^\\n\" +\n\t\"\\x10IngestIdentifier\\x12#\\n\" +\n\t\"\\rworkload_name\\x18\\x01 \\x01(\\tR\\fworkloadName\\x12%\\n\" +\n\t\"\\x0econtainer_name\\x18\\x02 \\x01(\\tR\\rcontainerName\\\"\\xc5\\x01\\n\" +\n\t\"\\rIngestRequest\\x12H\\n\" +\n\t\"\\n\" +\n\t\"identifier\\x18\\x01 \\x01(\\v2(.telepresence.connector.IngestIdentifierR\\n\" +\n\t\"identifier\\x12\\x1f\\n\" +\n\t\"\\vmount_point\\x18\\x02 \\x01(\\tR\\n\" +\n\t\"mountPoint\\x12(\\n\" +\n\t\"\\x10local_mount_port\\x18\\x03 \\x01(\\x05R\\x0elocalMountPort\\x12\\x1f\\n\" +\n\t\"\\vlocal_ports\\x18\\x04 \\x03(\\tR\\n\" +\n\t\"localPorts\\\"\\xa3\\x04\\n\" +\n\t\"\\n\" +\n\t\"IngestInfo\\x12\\x1a\\n\" +\n\t\"\\bworkload\\x18\\x01 \\x01(\\tR\\bworkload\\x12#\\n\" +\n\t\"\\rworkload_kind\\x18\\x02 \\x01(\\tR\\fworkloadKind\\x12\\x1c\\n\" +\n\t\"\\tcontainer\\x18\\x03 \\x01(\\tR\\tcontainer\\x12\\x1f\\n\" +\n\t\"\\vmount_point\\x18\\x04 \\x01(\\tR\\n\" +\n\t\"mountPoint\\x12\\x15\\n\" +\n\t\"\\x06pod_ip\\x18\\x05 \\x01(\\tR\\x05podIp\\x12\\x1b\\n\" +\n\t\"\\tsftp_port\\x18\\x06 \\x01(\\x05R\\bsftpPort\\x12\\x19\\n\" +\n\t\"\\bftp_port\\x18\\a \\x01(\\x05R\\aftpPort\\x12U\\n\" +\n\t\"\\venvironment\\x18\\b \\x03(\\v23.telepresence.connector.IngestInfo.EnvironmentEntryR\\venvironment\\x12,\\n\" +\n\t\"\\x12client_mount_point\\x18\\t \\x01(\\tR\\x10clientMountPoint\\x12F\\n\" +\n\t\"\\x06mounts\\x18\\n\" +\n\t\" \\x03(\\v2..telepresence.connector.IngestInfo.MountsEntryR\\x06mounts\\x1a>\\n\" +\n\t\"\\x10EnvironmentEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\x1a9\\n\" +\n\t\"\\vMountsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\x05R\\x05value:\\x028\\x01\\\"7\\n\" +\n\t\"\\x15WatchWorkloadsRequest\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"namespaces\\x18\\x01 \\x03(\\tR\\n\" +\n\t\"namespaces\\\"\\xf8\\x02\\n\" +\n\t\"\\fWorkloadInfo\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x02 \\x01(\\tR\\tnamespace\\x128\\n\" +\n\t\"\\x18not_interceptable_reason\\x18\\x03 \\x01(\\tR\\x16notInterceptableReason\\x12J\\n\" +\n\t\"\\x0eintercept_info\\x18\\x04 \\x03(\\v2#.telepresence.manager.InterceptInfoR\\rinterceptInfo\\x12C\\n\" +\n\t\"\\vingest_info\\x18\\x05 \\x03(\\v2\\\".telepresence.connector.IngestInfoR\\n\" +\n\t\"ingestInfo\\x124\\n\" +\n\t\"\\x16workload_resource_type\\x18\\x06 \\x01(\\tR\\x14workloadResourceType\\x12\\x10\\n\" +\n\t\"\\x03uid\\x18\\a \\x01(\\tR\\x03uid\\x12#\\n\" +\n\t\"\\ragent_version\\x18\\b \\x01(\\tR\\fagentVersion\\\"Z\\n\" +\n\t\"\\x14WorkloadInfoSnapshot\\x12B\\n\" +\n\t\"\\tworkloads\\x18\\x01 \\x03(\\v2$.telepresence.connector.WorkloadInfoR\\tworkloads\\\"\\xe5\\x01\\n\" +\n\t\"\\x0fLogLevelRequest\\x12\\x1b\\n\" +\n\t\"\\tlog_level\\x18\\x01 \\x01(\\tR\\blogLevel\\x125\\n\" +\n\t\"\\bduration\\x18\\x02 \\x01(\\v2\\x19.google.protobuf.DurationR\\bduration\\x12C\\n\" +\n\t\"\\x05scope\\x18\\x03 \\x01(\\x0e2-.telepresence.connector.LogLevelRequest.ScopeR\\x05scope\\\"9\\n\" +\n\t\"\\x05Scope\\x12\\x0f\\n\" +\n\t\"\\vUNSPECIFIED\\x10\\x00\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"LOCAL_ONLY\\x10\\x01\\x12\\x0f\\n\" +\n\t\"\\vREMOTE_ONLY\\x10\\x02\\\"\\x8f\\x01\\n\" +\n\t\"\\vLogsRequest\\x12'\\n\" +\n\t\"\\x0ftraffic_manager\\x18\\x01 \\x01(\\bR\\x0etrafficManager\\x12 \\n\" +\n\t\"\\fget_pod_yaml\\x18\\x02 \\x01(\\bR\\n\" +\n\t\"getPodYaml\\x12\\x16\\n\" +\n\t\"\\x06agents\\x18\\x03 \\x01(\\tR\\x06agents\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"export_dir\\x18\\x04 \\x01(\\tR\\texportDir\\\"\\xae\\x01\\n\" +\n\t\"\\fLogsResponse\\x12\\x14\\n\" +\n\t\"\\x05error\\x18\\x01 \\x01(\\tR\\x05error\\x12L\\n\" +\n\t\"\\bpod_info\\x18\\x02 \\x03(\\v21.telepresence.connector.LogsResponse.PodInfoEntryR\\apodInfo\\x1a:\\n\" +\n\t\"\\fPodInfoEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\\"Z\\n\" +\n\t\"\\x14GetNamespacesRequest\\x12*\\n\" +\n\t\"\\x11for_client_access\\x18\\x01 \\x01(\\bR\\x0fforClientAccess\\x12\\x16\\n\" +\n\t\"\\x06prefix\\x18\\x02 \\x01(\\tR\\x06prefix\\\"7\\n\" +\n\t\"\\x15GetNamespacesResponse\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"namespaces\\x18\\x02 \\x03(\\tR\\n\" +\n\t\"namespaces\\\"\\\"\\n\" +\n\t\"\\fClientConfig\\x12\\x12\\n\" +\n\t\"\\x04json\\x18\\x01 \\x01(\\fR\\x04json\\\"\\x8c\\x01\\n\" +\n\t\"\\x0eClusterSubnets\\x12<\\n\" +\n\t\"\\vpod_subnets\\x18\\x01 \\x03(\\v2\\x1b.telepresence.manager.IPNetR\\n\" +\n\t\"podSubnets\\x12<\\n\" +\n\t\"\\vsvc_subnets\\x18\\x02 \\x03(\\v2\\x1b.telepresence.manager.IPNetR\\n\" +\n\t\"svcSubnets\\\"F\\n\" +\n\t\"\\x17ResolveSyntheticRequest\\x12\\x0e\\n\" +\n\t\"\\x02ip\\x18\\x01 \\x01(\\fR\\x02ip\\x12\\x1b\\n\" +\n\t\"\\tname_only\\x18\\x02 \\x01(\\bR\\bnameOnly\\\"O\\n\" +\n\t\"\\x18ResolveSyntheticResponse\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x1f\\n\" +\n\t\"\\vresolved_ip\\x18\\x02 \\x01(\\fR\\n\" +\n\t\"resolvedIp\\\";\\n\" +\n\t\"\\x16RevokeInterceptRequest\\x12!\\n\" +\n\t\"\\fintercept_id\\x18\\x01 \\x01(\\tR\\vinterceptId2\\xcd\\x18\\n\" +\n\t\"\\tConnector\\x12C\\n\" +\n\t\"\\aVersion\\x12\\x16.google.protobuf.Empty\\x1a .telepresence.common.VersionInfo\\x12M\\n\" +\n\t\"\\x11RootDaemonVersion\\x12\\x16.google.protobuf.Empty\\x1a .telepresence.common.VersionInfo\\x12Q\\n\" +\n\t\"\\x15TrafficManagerVersion\\x12\\x16.google.protobuf.Empty\\x1a .telepresence.common.VersionInfo\\x12L\\n\" +\n\t\"\\rAgentImageFQN\\x12\\x16.google.protobuf.Empty\\x1a#.telepresence.manager.AgentImageFQN\\x12^\\n\" +\n\t\"\\fGetIntercept\\x12).telepresence.manager.GetInterceptRequest\\x1a#.telepresence.manager.InterceptInfo\\x12V\\n\" +\n\t\"\\aConnect\\x12&.telepresence.connector.ConnectRequest\\x1a#.telepresence.connector.ConnectInfo\\x12<\\n\" +\n\t\"\\n\" +\n\t\"Disconnect\\x12\\x16.google.protobuf.Empty\\x1a\\x16.google.protobuf.Empty\\x12S\\n\" +\n\t\"\\x11GetClusterSubnets\\x12\\x16.google.protobuf.Empty\\x1a&.telepresence.connector.ClusterSubnets\\x12E\\n\" +\n\t\"\\x06Status\\x12\\x16.google.protobuf.Empty\\x1a#.telepresence.connector.ConnectInfo\\x12V\\n\" +\n\t\"\\fCanIntercept\\x12..telepresence.connector.CreateInterceptRequest\\x1a\\x16.google.protobuf.Empty\\x12S\\n\" +\n\t\"\\x06Ingest\\x12%.telepresence.connector.IngestRequest\\x1a\\\".telepresence.connector.IngestInfo\\x12Y\\n\" +\n\t\"\\tGetIngest\\x12(.telepresence.connector.IngestIdentifier\\x1a\\\".telepresence.connector.IngestInfo\\x12[\\n\" +\n\t\"\\vLeaveIngest\\x12(.telepresence.connector.IngestIdentifier\\x1a\\\".telepresence.connector.IngestInfo\\x12f\\n\" +\n\t\"\\x0fCreateIntercept\\x12..telepresence.connector.CreateInterceptRequest\\x1a#.telepresence.manager.InterceptInfo\\x12X\\n\" +\n\t\"\\x0fRemoveIntercept\\x12-.telepresence.manager.RemoveInterceptRequest2\\x1a\\x16.google.protobuf.Empty\\x12Y\\n\" +\n\t\"\\x0fRevokeIntercept\\x12..telepresence.connector.RevokeInterceptRequest\\x1a\\x16.google.protobuf.Empty\\x12M\\n\" +\n\t\"\\tUninstall\\x12(.telepresence.connector.UninstallRequest\\x1a\\x16.google.protobuf.Empty\\x12Y\\n\" +\n\t\"\\x04List\\x12#.telepresence.connector.ListRequest\\x1a,.telepresence.connector.WorkloadInfoSnapshot\\x12o\\n\" +\n\t\"\\x0eWatchWorkloads\\x12-.telepresence.connector.WatchWorkloadsRequest\\x1a,.telepresence.connector.WorkloadInfoSnapshot0\\x01\\x12N\\n\" +\n\t\"\\vSetLogLevel\\x12'.telepresence.connector.LogLevelRequest\\x1a\\x16.google.protobuf.Empty\\x12A\\n\" +\n\t\"\\x04Quit\\x12\\x16.google.protobuf.Empty\\x1a!.telepresence.daemon.QuitResponse\\x12W\\n\" +\n\t\"\\n\" +\n\t\"GatherLogs\\x12#.telepresence.connector.LogsRequest\\x1a$.telepresence.connector.LogsResponse\\x12M\\n\" +\n\t\"\\x0eAddInterceptor\\x12#.telepresence.connector.Interceptor\\x1a\\x16.google.protobuf.Empty\\x12P\\n\" +\n\t\"\\x11RemoveInterceptor\\x12#.telepresence.connector.Interceptor\\x1a\\x16.google.protobuf.Empty\\x12l\\n\" +\n\t\"\\rGetNamespaces\\x12,.telepresence.connector.GetNamespacesRequest\\x1a-.telepresence.connector.GetNamespacesResponse\\x12Y\\n\" +\n\t\"\\x15GetKnownWorkloadKinds\\x12\\x16.google.protobuf.Empty\\x1a(.telepresence.manager.KnownWorkloadKinds\\x12I\\n\" +\n\t\"\\x17RemoteMountAvailability\\x12\\x16.google.protobuf.Empty\\x1a\\x16.google.protobuf.Empty\\x12I\\n\" +\n\t\"\\tGetConfig\\x12\\x16.google.protobuf.Empty\\x1a$.telepresence.connector.ClientConfig\\x12T\\n\" +\n\t\"\\x0eSetDNSExcludes\\x12*.telepresence.daemon.SetDNSExcludesRequest\\x1a\\x16.google.protobuf.Empty\\x12T\\n\" +\n\t\"\\x0eSetDNSMappings\\x12*.telepresence.daemon.SetDNSMappingsRequest\\x1a\\x16.google.protobuf.Empty\\x12e\\n\" +\n\t\"\\x0eGetAgentConfig\\x12(.telepresence.manager.AgentConfigRequest\\x1a).telepresence.manager.AgentConfigResponse\\x12w\\n\" +\n\t\"\\x12ResolveSyntheticIP\\x12/.telepresence.connector.ResolveSyntheticRequest\\x1a0.telepresence.connector.ResolveSyntheticResponse\\x12W\\n\" +\n\t\"\\bLookupIP\\x12$.telepresence.daemon.LookupIPRequest\\x1a%.telepresence.daemon.LookupIPResponse\\x12`\\n\" +\n\t\"\\vResolvePort\\x12'.telepresence.daemon.ResolvePortRequest\\x1a(.telepresence.daemon.ResolvePortResponse\\x12S\\n\" +\n\t\"\\x10RerouteLocalPort\\x12'.telepresence.daemon.ReroutePortRequest\\x1a\\x16.google.protobuf.Empty\\x12T\\n\" +\n\t\"\\x11RerouteRemotePort\\x12'.telepresence.daemon.ReroutePortRequest\\x1a\\x16.google.protobuf.EmptyB9Z7github.com/telepresenceio/telepresence/rpc/v2/connectorb\\x06proto3\"\n\nvar (\n\tfile_connector_connector_proto_rawDescOnce sync.Once\n\tfile_connector_connector_proto_rawDescData []byte\n)\n\nfunc file_connector_connector_proto_rawDescGZIP() []byte {\n\tfile_connector_connector_proto_rawDescOnce.Do(func() {\n\t\tfile_connector_connector_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_connector_connector_proto_rawDesc), len(file_connector_connector_proto_rawDesc)))\n\t})\n\treturn file_connector_connector_proto_rawDescData\n}\n\nvar file_connector_connector_proto_enumTypes = make([]protoimpl.EnumInfo, 3)\nvar file_connector_connector_proto_msgTypes = make([]protoimpl.MessageInfo, 28)\nvar file_connector_connector_proto_goTypes = []any{\n\t(UninstallRequest_UninstallType)(0),     // 0: telepresence.connector.UninstallRequest.UninstallType\n\t(ListRequest_Filter)(0),                 // 1: telepresence.connector.ListRequest.Filter\n\t(LogLevelRequest_Scope)(0),              // 2: telepresence.connector.LogLevelRequest.Scope\n\t(*Interceptor)(nil),                     // 3: telepresence.connector.Interceptor\n\t(*ConnectRequest)(nil),                  // 4: telepresence.connector.ConnectRequest\n\t(*ConnectInfo)(nil),                     // 5: telepresence.connector.ConnectInfo\n\t(*UninstallRequest)(nil),                // 6: telepresence.connector.UninstallRequest\n\t(*CreateInterceptRequest)(nil),          // 7: telepresence.connector.CreateInterceptRequest\n\t(*ListRequest)(nil),                     // 8: telepresence.connector.ListRequest\n\t(*IngestIdentifier)(nil),                // 9: telepresence.connector.IngestIdentifier\n\t(*IngestRequest)(nil),                   // 10: telepresence.connector.IngestRequest\n\t(*IngestInfo)(nil),                      // 11: telepresence.connector.IngestInfo\n\t(*WatchWorkloadsRequest)(nil),           // 12: telepresence.connector.WatchWorkloadsRequest\n\t(*WorkloadInfo)(nil),                    // 13: telepresence.connector.WorkloadInfo\n\t(*WorkloadInfoSnapshot)(nil),            // 14: telepresence.connector.WorkloadInfoSnapshot\n\t(*LogLevelRequest)(nil),                 // 15: telepresence.connector.LogLevelRequest\n\t(*LogsRequest)(nil),                     // 16: telepresence.connector.LogsRequest\n\t(*LogsResponse)(nil),                    // 17: telepresence.connector.LogsResponse\n\t(*GetNamespacesRequest)(nil),            // 18: telepresence.connector.GetNamespacesRequest\n\t(*GetNamespacesResponse)(nil),           // 19: telepresence.connector.GetNamespacesResponse\n\t(*ClientConfig)(nil),                    // 20: telepresence.connector.ClientConfig\n\t(*ClusterSubnets)(nil),                  // 21: telepresence.connector.ClusterSubnets\n\t(*ResolveSyntheticRequest)(nil),         // 22: telepresence.connector.ResolveSyntheticRequest\n\t(*ResolveSyntheticResponse)(nil),        // 23: telepresence.connector.ResolveSyntheticResponse\n\t(*RevokeInterceptRequest)(nil),          // 24: telepresence.connector.RevokeInterceptRequest\n\tnil,                                     // 25: telepresence.connector.ConnectRequest.KubeFlagsEntry\n\tnil,                                     // 26: telepresence.connector.ConnectRequest.EnvironmentEntry\n\tnil,                                     // 27: telepresence.connector.ConnectInfo.KubeFlagsEntry\n\tnil,                                     // 28: telepresence.connector.IngestInfo.EnvironmentEntry\n\tnil,                                     // 29: telepresence.connector.IngestInfo.MountsEntry\n\tnil,                                     // 30: telepresence.connector.LogsResponse.PodInfoEntry\n\t(*daemon.SubnetViaWorkload)(nil),        // 31: telepresence.daemon.SubnetViaWorkload\n\t(*common.VersionInfo)(nil),              // 32: telepresence.common.VersionInfo\n\t(*manager.InterceptInfoSnapshot)(nil),   // 33: telepresence.manager.InterceptInfoSnapshot\n\t(*manager.SessionInfo)(nil),             // 34: telepresence.manager.SessionInfo\n\t(*manager.VersionInfo2)(nil),            // 35: telepresence.manager.VersionInfo2\n\t(*daemon.DaemonStatus)(nil),             // 36: telepresence.daemon.DaemonStatus\n\t(*manager.InterceptSpec)(nil),           // 37: telepresence.manager.InterceptSpec\n\t(*manager.InterceptInfo)(nil),           // 38: telepresence.manager.InterceptInfo\n\t(*durationpb.Duration)(nil),             // 39: google.protobuf.Duration\n\t(*manager.IPNet)(nil),                   // 40: telepresence.manager.IPNet\n\t(*emptypb.Empty)(nil),                   // 41: google.protobuf.Empty\n\t(*manager.GetInterceptRequest)(nil),     // 42: telepresence.manager.GetInterceptRequest\n\t(*manager.RemoveInterceptRequest2)(nil), // 43: telepresence.manager.RemoveInterceptRequest2\n\t(*daemon.SetDNSExcludesRequest)(nil),    // 44: telepresence.daemon.SetDNSExcludesRequest\n\t(*daemon.SetDNSMappingsRequest)(nil),    // 45: telepresence.daemon.SetDNSMappingsRequest\n\t(*manager.AgentConfigRequest)(nil),      // 46: telepresence.manager.AgentConfigRequest\n\t(*daemon.LookupIPRequest)(nil),          // 47: telepresence.daemon.LookupIPRequest\n\t(*daemon.ResolvePortRequest)(nil),       // 48: telepresence.daemon.ResolvePortRequest\n\t(*daemon.ReroutePortRequest)(nil),       // 49: telepresence.daemon.ReroutePortRequest\n\t(*manager.AgentImageFQN)(nil),           // 50: telepresence.manager.AgentImageFQN\n\t(*daemon.QuitResponse)(nil),             // 51: telepresence.daemon.QuitResponse\n\t(*manager.KnownWorkloadKinds)(nil),      // 52: telepresence.manager.KnownWorkloadKinds\n\t(*manager.AgentConfigResponse)(nil),     // 53: telepresence.manager.AgentConfigResponse\n\t(*daemon.LookupIPResponse)(nil),         // 54: telepresence.daemon.LookupIPResponse\n\t(*daemon.ResolvePortResponse)(nil),      // 55: telepresence.daemon.ResolvePortResponse\n}\nvar file_connector_connector_proto_depIdxs = []int32{\n\t25, // 0: telepresence.connector.ConnectRequest.kube_flags:type_name -> telepresence.connector.ConnectRequest.KubeFlagsEntry\n\t31, // 1: telepresence.connector.ConnectRequest.subnet_via_workloads:type_name -> telepresence.daemon.SubnetViaWorkload\n\t26, // 2: telepresence.connector.ConnectRequest.environment:type_name -> telepresence.connector.ConnectRequest.EnvironmentEntry\n\t32, // 3: telepresence.connector.ConnectInfo.version:type_name -> telepresence.common.VersionInfo\n\t27, // 4: telepresence.connector.ConnectInfo.kube_flags:type_name -> telepresence.connector.ConnectInfo.KubeFlagsEntry\n\t33, // 5: telepresence.connector.ConnectInfo.intercepts:type_name -> telepresence.manager.InterceptInfoSnapshot\n\t11, // 6: telepresence.connector.ConnectInfo.ingests:type_name -> telepresence.connector.IngestInfo\n\t34, // 7: telepresence.connector.ConnectInfo.session_info:type_name -> telepresence.manager.SessionInfo\n\t35, // 8: telepresence.connector.ConnectInfo.manager_version:type_name -> telepresence.manager.VersionInfo2\n\t36, // 9: telepresence.connector.ConnectInfo.daemon_status:type_name -> telepresence.daemon.DaemonStatus\n\t31, // 10: telepresence.connector.ConnectInfo.subnet_via_workloads:type_name -> telepresence.daemon.SubnetViaWorkload\n\t0,  // 11: telepresence.connector.UninstallRequest.uninstall_type:type_name -> telepresence.connector.UninstallRequest.UninstallType\n\t37, // 12: telepresence.connector.CreateInterceptRequest.spec:type_name -> telepresence.manager.InterceptSpec\n\t1,  // 13: telepresence.connector.ListRequest.filter:type_name -> telepresence.connector.ListRequest.Filter\n\t9,  // 14: telepresence.connector.IngestRequest.identifier:type_name -> telepresence.connector.IngestIdentifier\n\t28, // 15: telepresence.connector.IngestInfo.environment:type_name -> telepresence.connector.IngestInfo.EnvironmentEntry\n\t29, // 16: telepresence.connector.IngestInfo.mounts:type_name -> telepresence.connector.IngestInfo.MountsEntry\n\t38, // 17: telepresence.connector.WorkloadInfo.intercept_info:type_name -> telepresence.manager.InterceptInfo\n\t11, // 18: telepresence.connector.WorkloadInfo.ingest_info:type_name -> telepresence.connector.IngestInfo\n\t13, // 19: telepresence.connector.WorkloadInfoSnapshot.workloads:type_name -> telepresence.connector.WorkloadInfo\n\t39, // 20: telepresence.connector.LogLevelRequest.duration:type_name -> google.protobuf.Duration\n\t2,  // 21: telepresence.connector.LogLevelRequest.scope:type_name -> telepresence.connector.LogLevelRequest.Scope\n\t30, // 22: telepresence.connector.LogsResponse.pod_info:type_name -> telepresence.connector.LogsResponse.PodInfoEntry\n\t40, // 23: telepresence.connector.ClusterSubnets.pod_subnets:type_name -> telepresence.manager.IPNet\n\t40, // 24: telepresence.connector.ClusterSubnets.svc_subnets:type_name -> telepresence.manager.IPNet\n\t41, // 25: telepresence.connector.Connector.Version:input_type -> google.protobuf.Empty\n\t41, // 26: telepresence.connector.Connector.RootDaemonVersion:input_type -> google.protobuf.Empty\n\t41, // 27: telepresence.connector.Connector.TrafficManagerVersion:input_type -> google.protobuf.Empty\n\t41, // 28: telepresence.connector.Connector.AgentImageFQN:input_type -> google.protobuf.Empty\n\t42, // 29: telepresence.connector.Connector.GetIntercept:input_type -> telepresence.manager.GetInterceptRequest\n\t4,  // 30: telepresence.connector.Connector.Connect:input_type -> telepresence.connector.ConnectRequest\n\t41, // 31: telepresence.connector.Connector.Disconnect:input_type -> google.protobuf.Empty\n\t41, // 32: telepresence.connector.Connector.GetClusterSubnets:input_type -> google.protobuf.Empty\n\t41, // 33: telepresence.connector.Connector.Status:input_type -> google.protobuf.Empty\n\t7,  // 34: telepresence.connector.Connector.CanIntercept:input_type -> telepresence.connector.CreateInterceptRequest\n\t10, // 35: telepresence.connector.Connector.Ingest:input_type -> telepresence.connector.IngestRequest\n\t9,  // 36: telepresence.connector.Connector.GetIngest:input_type -> telepresence.connector.IngestIdentifier\n\t9,  // 37: telepresence.connector.Connector.LeaveIngest:input_type -> telepresence.connector.IngestIdentifier\n\t7,  // 38: telepresence.connector.Connector.CreateIntercept:input_type -> telepresence.connector.CreateInterceptRequest\n\t43, // 39: telepresence.connector.Connector.RemoveIntercept:input_type -> telepresence.manager.RemoveInterceptRequest2\n\t24, // 40: telepresence.connector.Connector.RevokeIntercept:input_type -> telepresence.connector.RevokeInterceptRequest\n\t6,  // 41: telepresence.connector.Connector.Uninstall:input_type -> telepresence.connector.UninstallRequest\n\t8,  // 42: telepresence.connector.Connector.List:input_type -> telepresence.connector.ListRequest\n\t12, // 43: telepresence.connector.Connector.WatchWorkloads:input_type -> telepresence.connector.WatchWorkloadsRequest\n\t15, // 44: telepresence.connector.Connector.SetLogLevel:input_type -> telepresence.connector.LogLevelRequest\n\t41, // 45: telepresence.connector.Connector.Quit:input_type -> google.protobuf.Empty\n\t16, // 46: telepresence.connector.Connector.GatherLogs:input_type -> telepresence.connector.LogsRequest\n\t3,  // 47: telepresence.connector.Connector.AddInterceptor:input_type -> telepresence.connector.Interceptor\n\t3,  // 48: telepresence.connector.Connector.RemoveInterceptor:input_type -> telepresence.connector.Interceptor\n\t18, // 49: telepresence.connector.Connector.GetNamespaces:input_type -> telepresence.connector.GetNamespacesRequest\n\t41, // 50: telepresence.connector.Connector.GetKnownWorkloadKinds:input_type -> google.protobuf.Empty\n\t41, // 51: telepresence.connector.Connector.RemoteMountAvailability:input_type -> google.protobuf.Empty\n\t41, // 52: telepresence.connector.Connector.GetConfig:input_type -> google.protobuf.Empty\n\t44, // 53: telepresence.connector.Connector.SetDNSExcludes:input_type -> telepresence.daemon.SetDNSExcludesRequest\n\t45, // 54: telepresence.connector.Connector.SetDNSMappings:input_type -> telepresence.daemon.SetDNSMappingsRequest\n\t46, // 55: telepresence.connector.Connector.GetAgentConfig:input_type -> telepresence.manager.AgentConfigRequest\n\t22, // 56: telepresence.connector.Connector.ResolveSyntheticIP:input_type -> telepresence.connector.ResolveSyntheticRequest\n\t47, // 57: telepresence.connector.Connector.LookupIP:input_type -> telepresence.daemon.LookupIPRequest\n\t48, // 58: telepresence.connector.Connector.ResolvePort:input_type -> telepresence.daemon.ResolvePortRequest\n\t49, // 59: telepresence.connector.Connector.RerouteLocalPort:input_type -> telepresence.daemon.ReroutePortRequest\n\t49, // 60: telepresence.connector.Connector.RerouteRemotePort:input_type -> telepresence.daemon.ReroutePortRequest\n\t32, // 61: telepresence.connector.Connector.Version:output_type -> telepresence.common.VersionInfo\n\t32, // 62: telepresence.connector.Connector.RootDaemonVersion:output_type -> telepresence.common.VersionInfo\n\t32, // 63: telepresence.connector.Connector.TrafficManagerVersion:output_type -> telepresence.common.VersionInfo\n\t50, // 64: telepresence.connector.Connector.AgentImageFQN:output_type -> telepresence.manager.AgentImageFQN\n\t38, // 65: telepresence.connector.Connector.GetIntercept:output_type -> telepresence.manager.InterceptInfo\n\t5,  // 66: telepresence.connector.Connector.Connect:output_type -> telepresence.connector.ConnectInfo\n\t41, // 67: telepresence.connector.Connector.Disconnect:output_type -> google.protobuf.Empty\n\t21, // 68: telepresence.connector.Connector.GetClusterSubnets:output_type -> telepresence.connector.ClusterSubnets\n\t5,  // 69: telepresence.connector.Connector.Status:output_type -> telepresence.connector.ConnectInfo\n\t41, // 70: telepresence.connector.Connector.CanIntercept:output_type -> google.protobuf.Empty\n\t11, // 71: telepresence.connector.Connector.Ingest:output_type -> telepresence.connector.IngestInfo\n\t11, // 72: telepresence.connector.Connector.GetIngest:output_type -> telepresence.connector.IngestInfo\n\t11, // 73: telepresence.connector.Connector.LeaveIngest:output_type -> telepresence.connector.IngestInfo\n\t38, // 74: telepresence.connector.Connector.CreateIntercept:output_type -> telepresence.manager.InterceptInfo\n\t41, // 75: telepresence.connector.Connector.RemoveIntercept:output_type -> google.protobuf.Empty\n\t41, // 76: telepresence.connector.Connector.RevokeIntercept:output_type -> google.protobuf.Empty\n\t41, // 77: telepresence.connector.Connector.Uninstall:output_type -> google.protobuf.Empty\n\t14, // 78: telepresence.connector.Connector.List:output_type -> telepresence.connector.WorkloadInfoSnapshot\n\t14, // 79: telepresence.connector.Connector.WatchWorkloads:output_type -> telepresence.connector.WorkloadInfoSnapshot\n\t41, // 80: telepresence.connector.Connector.SetLogLevel:output_type -> google.protobuf.Empty\n\t51, // 81: telepresence.connector.Connector.Quit:output_type -> telepresence.daemon.QuitResponse\n\t17, // 82: telepresence.connector.Connector.GatherLogs:output_type -> telepresence.connector.LogsResponse\n\t41, // 83: telepresence.connector.Connector.AddInterceptor:output_type -> google.protobuf.Empty\n\t41, // 84: telepresence.connector.Connector.RemoveInterceptor:output_type -> google.protobuf.Empty\n\t19, // 85: telepresence.connector.Connector.GetNamespaces:output_type -> telepresence.connector.GetNamespacesResponse\n\t52, // 86: telepresence.connector.Connector.GetKnownWorkloadKinds:output_type -> telepresence.manager.KnownWorkloadKinds\n\t41, // 87: telepresence.connector.Connector.RemoteMountAvailability:output_type -> google.protobuf.Empty\n\t20, // 88: telepresence.connector.Connector.GetConfig:output_type -> telepresence.connector.ClientConfig\n\t41, // 89: telepresence.connector.Connector.SetDNSExcludes:output_type -> google.protobuf.Empty\n\t41, // 90: telepresence.connector.Connector.SetDNSMappings:output_type -> google.protobuf.Empty\n\t53, // 91: telepresence.connector.Connector.GetAgentConfig:output_type -> telepresence.manager.AgentConfigResponse\n\t23, // 92: telepresence.connector.Connector.ResolveSyntheticIP:output_type -> telepresence.connector.ResolveSyntheticResponse\n\t54, // 93: telepresence.connector.Connector.LookupIP:output_type -> telepresence.daemon.LookupIPResponse\n\t55, // 94: telepresence.connector.Connector.ResolvePort:output_type -> telepresence.daemon.ResolvePortResponse\n\t41, // 95: telepresence.connector.Connector.RerouteLocalPort:output_type -> google.protobuf.Empty\n\t41, // 96: telepresence.connector.Connector.RerouteRemotePort:output_type -> google.protobuf.Empty\n\t61, // [61:97] is the sub-list for method output_type\n\t25, // [25:61] is the sub-list for method input_type\n\t25, // [25:25] is the sub-list for extension type_name\n\t25, // [25:25] is the sub-list for extension extendee\n\t0,  // [0:25] is the sub-list for field type_name\n}\n\nfunc init() { file_connector_connector_proto_init() }\nfunc file_connector_connector_proto_init() {\n\tif File_connector_connector_proto != nil {\n\t\treturn\n\t}\n\tfile_connector_connector_proto_msgTypes[1].OneofWrappers = []any{}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_connector_connector_proto_rawDesc), len(file_connector_connector_proto_rawDesc)),\n\t\t\tNumEnums:      3,\n\t\t\tNumMessages:   28,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_connector_connector_proto_goTypes,\n\t\tDependencyIndexes: file_connector_connector_proto_depIdxs,\n\t\tEnumInfos:         file_connector_connector_proto_enumTypes,\n\t\tMessageInfos:      file_connector_connector_proto_msgTypes,\n\t}.Build()\n\tFile_connector_connector_proto = out.File\n\tfile_connector_connector_proto_goTypes = nil\n\tfile_connector_connector_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "rpc/connector/connector.proto",
    "content": "syntax = \"proto3\";\npackage telepresence.connector;\n\nimport \"common/version.proto\";\nimport \"daemon/daemon.proto\";\nimport \"google/protobuf/duration.proto\";\nimport \"google/protobuf/empty.proto\";\nimport \"manager/manager.proto\";\n\noption go_package = \"github.com/telepresenceio/telepresence/rpc/v2/connector\";\n\n// The Connector service is responsible for connecting to the traffic manager\n// and manage intercepts. It can only run when a Daemon is running.\nservice Connector {\n  // Returns version information from the Connector\n  rpc Version(google.protobuf.Empty) returns (telepresence.common.VersionInfo);\n\n  // Returns version information from the Root Daemon\n  rpc RootDaemonVersion(google.protobuf.Empty) returns (telepresence.common.VersionInfo);\n\n  // Returns version information from the Traffic Manager\n  rpc TrafficManagerVersion(google.protobuf.Empty) returns (telepresence.common.VersionInfo);\n\n  // Returns the fully qualified image name of the traffic-agent that the agent-injector is configured to inject.\n  rpc AgentImageFQN(google.protobuf.Empty) returns (manager.AgentImageFQN);\n\n  // GetIntercept gets info from intercept name\n  rpc GetIntercept(manager.GetInterceptRequest) returns (manager.InterceptInfo);\n\n  // Connects to the cluster and connects the laptop's network (via\n  // the daemon process) to the cluster's network.  A result code of\n  // UNSPECIFIED indicates that the connection was successfully\n  // initiated; if already connected, then either ALREADY_CONNECTED or\n  // MUST_RESTART is returned, based on whether the current connection\n  // is in agreement with the ConnectionRequest.\n  rpc Connect(ConnectRequest) returns (ConnectInfo);\n\n  // Disconnects the cluster\n  rpc Disconnect(google.protobuf.Empty) returns (google.protobuf.Empty);\n\n  // GetClusterSubnets gets the outbound info that has been set on daemon\n  rpc GetClusterSubnets(google.protobuf.Empty) returns (ClusterSubnets);\n\n  // Status returns the status of the current connection or DISCONNECTED\n  // if no connection has been established.\n  rpc Status(google.protobuf.Empty) returns (ConnectInfo);\n\n  // Queries the connector whether it is possible to create the given intercept\n  // and returns an error if it's not.\n  rpc CanIntercept(CreateInterceptRequest) returns (google.protobuf.Empty);\n\n  // Starts an Ingest session.\n  rpc Ingest(IngestRequest) returns (IngestInfo);\n\n  // Get info about an ongoing Ingest.\n  rpc GetIngest(IngestIdentifier) returns (IngestInfo);\n\n  // Ends an Ingest session.\n  rpc LeaveIngest(IngestIdentifier) returns (IngestInfo);\n\n  // Adds an intercept to a workload.  Requires having already called\n  // Connect.\n  rpc CreateIntercept(CreateInterceptRequest) returns (manager.InterceptInfo);\n\n  // Deactivates and removes an existent workload intercept.\n  // Requires having already called Connect.\n  rpc RemoveIntercept(manager.RemoveInterceptRequest2) returns (google.protobuf.Empty);\n\n  // Revokes an intercept by intercept ID. This is an administrative operation that\n  // requires RBAC permissions to modify the \"traffic-manager\" configmap.\n  rpc RevokeIntercept(RevokeInterceptRequest) returns (google.protobuf.Empty);\n\n  // Uninstalls traffic-agents from the cluster.\n  // Requires having already called Connect.\n  rpc Uninstall(UninstallRequest) returns (google.protobuf.Empty);\n\n  // Returns a list of workloads and their current intercept status.\n  // Requires having already called Connect.\n  rpc List(ListRequest) returns (WorkloadInfoSnapshot);\n\n  // Watch all workloads in the mapped namespaces\n  rpc WatchWorkloads(WatchWorkloadsRequest) returns (stream WorkloadInfoSnapshot);\n\n  // SetLogLevel will temporarily change the log-level of the traffic-manager, traffic-agent, and user and root daemons.\n  rpc SetLogLevel(LogLevelRequest) returns (google.protobuf.Empty);\n\n  // Quits (terminates) the connector process.\n  rpc Quit(google.protobuf.Empty) returns (daemon.QuitResponse);\n\n  // GatherLogs will acquire logs for the various Telepresence components in kubernetes\n  // (pending the request) and return them to the caller\n  rpc GatherLogs(LogsRequest) returns (LogsResponse);\n\n  // AddInterceptor tells the connector that a given process is serving a specific\n  // intercept. The connector must kill this process when the intercept ends\n  rpc AddInterceptor(Interceptor) returns  (google.protobuf.Empty);\n\n  // RemoveInterceptor removes a previously added interceptor\n  rpc RemoveInterceptor(Interceptor)  returns  (google.protobuf.Empty);\n\n  // GetNamespaces gets the mapped namespaces with an optional prefix\n  rpc GetNamespaces(GetNamespacesRequest) returns (GetNamespacesResponse);\n\n  // GetKnownWorkloadKinds returns the known workload kinds\n  rpc GetKnownWorkloadKinds(google.protobuf.Empty) returns (manager.KnownWorkloadKinds);\n\n  // RemoteMountAvailability checks if remote mounts are possible using the given\n  // mount type and returns an error if its not.\n  rpc RemoteMountAvailability(google.protobuf.Empty) returns (google.protobuf.Empty);\n\n  // GetConfig returns the current configuration\n  rpc GetConfig(google.protobuf.Empty) returns (ClientConfig);\n\n  // SetDNSExcludes sets the excludes field of DNSConfig.\n  rpc SetDNSExcludes(daemon.SetDNSExcludesRequest) returns (google.protobuf.Empty);\n\n  // SetDNSMappings sets the Mappings field of DNSConfig.\n  rpc SetDNSMappings(daemon.SetDNSMappingsRequest) returns (google.protobuf.Empty);\n\n  // GetAgentConfig returns the agent configuration for a specific workload.\n  rpc GetAgentConfig(manager.AgentConfigRequest) returns (manager.AgentConfigResponse);\n\n  // ResolveSyntheticIP resolves a synthetic IP into a name and optionally\n  // the first IP for that name when the daemon performs a DNS lookup.\n  rpc ResolveSyntheticIP(ResolveSyntheticRequest) returns (ResolveSyntheticResponse);\n\n  // LookupIP resolves the given name using the Telepresence DNS server\n  rpc LookupIP(daemon.LookupIPRequest) returns (daemon.LookupIPResponse);\n\n  // ResolvePort resolves an host:port string from into a netip.AddrPort\n  rpc ResolvePort(daemon.ResolvePortRequest) returns (daemon.ResolvePortResponse);\n\n  // RerouteLocalPort reroutes a port on localhost to a netip.AddrPort.\n  rpc RerouteLocalPort(daemon.ReroutePortRequest) returns (google.protobuf.Empty);\n\n  // RerouteRemotePort makes a netip.AddrPort available on a new port on the same address.\n  rpc RerouteRemotePort(daemon.ReroutePortRequest) returns (google.protobuf.Empty);\n}\n\nmessage Interceptor {\n  // The ID of the intercept that is served by this interceptor process\n  string intercept_id = 1;\n  // The pid of the interceptor process\n  int32  pid = 2;\n  // Name or ID of container, in case the intercept handler runs in Docker\n  string container_name = 3;\n}\n\n// ConnectRequest contains the information needed to connect ot a cluster.\nmessage ConnectRequest {\n  // The kubernetes flags from the telepresence connect command\n  map<string, string> kube_flags = 1;\n\n  repeated string mapped_namespaces = 2;\n  string name = 3;\n  bool is_pod_daemon = 4;\n  repeated string also_proxy = 5; // protolint:disable:this REPEATED_FIELD_NAMES_PLURALIZED\n  repeated string never_proxy = 6; // protolint:disable:this REPEATED_FIELD_NAMES_PLURALIZED\n  repeated string allow_conflicting_subnets = 10;\n  repeated daemon.SubnetViaWorkload subnet_via_workloads = 11;\n  string manager_namespace = 7;\n  map<string, string> environment = 8;\n\n  // Kubeconfig YAML, if not to be loaded from file.\n  optional bytes kubeconfig_data = 12;\n\n  string client_id = 13;\n}\n\nmessage ConnectInfo {\n  string cluster_server = 3;\n  string cluster_context = 4;\n  common.VersionInfo version = 5;\n\n  // The name of the connection\n  string connection_name = 16;\n\n  // The kubernetes flags from the telepresence connect command when the connection was established\n  map<string, string> kube_flags = 17;\n\n  // the namespace that the connector is connected to.\n  string namespace = 6;\n\n  string manager_install_id = 7;\n\n  manager.InterceptInfoSnapshot intercepts = 8;\n\n  repeated IngestInfo ingests = 9;\n\n  manager.SessionInfo session_info = 10;\n\n  manager.VersionInfo2 manager_version = 19;\n\n  daemon.DaemonStatus daemon_status = 13;\n  string manager_namespace = 14;\n\n  repeated string mapped_namespaces = 15;\n  repeated daemon.SubnetViaWorkload subnet_via_workloads = 18;\n\n  // Connection was established by the caller's connect call.\n  bool initial = 20;\n\n  reserved 1, 2, 11, 12;\n}\n\nmessage UninstallRequest {\n  enum UninstallType {\n    UNSPECIFIED = 0;\n\n    // Uninstalls an agent from the named workloads\n    NAMED_AGENTS = 1;\n\n    // Uninstalls all agents\n    ALL_AGENTS = 2;\n  }\n\n  UninstallType uninstall_type = 1;\n  repeated string agents = 2;\n\n  // Namespace of agents to remove.\n  string namespace = 3;\n}\n\nmessage CreateInterceptRequest {\n  // No need to set spec.client; the connector will fill that in for\n  // you.\n  manager.InterceptSpec spec = 1;\n  string mount_point = 2;\n  string agent_image = 3;\n  bool is_pod_daemon = 4;\n  bytes extended_info = 5;\n  int32 local_mount_port = 6;\n  bool mount_read_only = 7;\n}\n\nmessage ListRequest {\n  // Bitmap filter\n  enum Filter {\n    UNSPECIFIED = 0;\n    INTERCEPTS = 1;\n    REPLACEMENTS = 2;\n    INGESTS = 4;\n    WIRETAPS = 8;\n    INSTALLED_AGENTS = 16;\n    EVERYTHING = 31;\n  }\n  Filter filter = 1;\n\n  // Namespace to list.\n  string namespace = 2;\n}\n\nmessage IngestIdentifier {\n  // Name of the workload that holds the desire container.\n  string workload_name = 1;\n\n  // The name of the desired container. Must be set when the workload contains more\n  // than one container candidate.\n  string container_name = 2;\n}\n\nmessage IngestRequest {\n  IngestIdentifier identifier = 1;\n\n  // Desired mount point. Can be set to \"true\" to generated temporary mount point,\n  // \"false\" to prevent that mounting takes place, an explicit path to use for the\n  // mount or an empty string in combination with a non-zero local_mount_port.\n  string mount_point = 2;\n\n  // Local port where an sftp client can connect when doing docker volume mounts.\n  int32 local_mount_port = 3;\n\n  // Extra ports that will be forwarded from the intercepting client's localhost\n  // to the intercepted pod. Each entry is a string containing a port number followed\n  // by an optional \"/TCP\" or \"/UDP\".\n  repeated string local_ports = 4;\n}\n\nmessage IngestInfo {\n  // Name of ingested workload\n  string workload = 1;\n\n  string workload_kind = 2;\n\n  // Name of ingested container\n  string container = 3;\n\n  // The directory where the intercept mounts can be found in the agent.\n  string mount_point = 4;\n\n  // The IP of the ingested pod.\n  string pod_ip = 5;\n\n  // The port where the SFTP server listens.\n  int32 sftp_port = 6;\n\n  // The port where the FTP server listens.\n  int32 ftp_port = 7;\n\n  // The environment of the ingested container\n  map<string, string> environment = 8;\n\n  // The directory where the client mounts the remote mount_point.\n  string client_mount_point = 9;\n\n  // Map of mount path -> MountPolicy\n  map<string, int32> mounts = 10;\n}\n\nmessage WatchWorkloadsRequest {\n  // Namespace to watch.\n  repeated string namespaces = 1;\n}\n\n// WorkloadInfo contains information about a workload\n// https://kubernetes.io/docs/concepts/workloads/\nmessage WorkloadInfo {\n  // Name of workload\n  string name = 1;\n\n  // Namespace of workload\n  string namespace = 2;\n\n  // Reason why workload cannot be intercepted, or empty if it can.\n  string not_interceptable_reason = 3;\n\n  // InterceptInfo reported from the traffic manager in case the workload is currently intercepted\n  repeated manager.InterceptInfo intercept_info = 4;\n\n  // IngestInfo reported from the traffic manager in case the workload is currently intercepted\n  repeated IngestInfo ingest_info = 5;\n\n  // Workload Resource type (e.g. Deployment, ReplicaSet, StatefulSet, Rollout)\n  string workload_resource_type = 6;\n\n  string uid = 7;\n\n  string agent_version = 8;\n}\n\nmessage WorkloadInfoSnapshot {\n  repeated WorkloadInfo workloads = 1;\n}\n\nmessage LogLevelRequest {\n  enum Scope {\n    UNSPECIFIED = 0;\n    LOCAL_ONLY = 1; // applies only to the local daemon processes\n    REMOTE_ONLY = 2; // applies only to traffic-manager and traffic-agents\n  }\n\n  string log_level = 1;\n\n  // The time that this log-level will be in effect before\n  // falling back to the configured log-level.\n  google.protobuf.Duration duration = 2;\n\n  Scope scope = 3;\n}\n\nmessage LogsRequest {\n  // Whether or not logs from the traffic-manager are desired.\n  bool traffic_manager = 1;\n\n  // Whether or not to get the pod yaml deployed to the cluster.\n  bool get_pod_yaml = 2;\n\n  // The traffic-agent(s) logs are desired from. Can be `all`, `False`,\n  // or substring to filter based on pod names.\n  string agents = 3;\n\n  // Directory that the logs will be exported to\n  string export_dir = 4;\n}\n\nmessage LogsResponse {\n  // General error that isn't associated with a pod such as failing to list the pods.\n  string error = 1;\n\n  // pod_info contains one entry per created file name name. The value is either the string\n  // \"ok\" indicating that the file exists, or an error string with info why it could not\n  // be created.\n  map<string, string> pod_info = 2;\n}\n\n\nmessage GetNamespacesRequest {\n  bool for_client_access = 1;\n  string prefix = 2;\n}\n\nmessage GetNamespacesResponse {\n  repeated string namespaces = 2;\n}\n\nmessage ClientConfig {\n  bytes json = 1;\n}\n\n// ClusterSubnets are the cluster subnets that the daemon has detected that need to be\n// routed\nmessage ClusterSubnets {\n  // pod_subnets are the subnets that pods go into\n  repeated manager.IPNet pod_subnets = 1;\n  // svc_subnets are subnets that services go into\n  repeated manager.IPNet svc_subnets = 2;\n}\n\nmessage ResolveSyntheticRequest {\n  // The synthetic IP to be resolved.\n  bytes ip = 1;\n\n  // Resolve the IP into a name, but do not make an attempt to resolve the name through DNS.\n  bool name_only = 2;\n}\n\nmessage ResolveSyntheticResponse {\n  // The name that the synthetic IP resolved to.\n  string name = 1;\n\n  // The IP resulting from a DNS lookup using the resolved name.\n  bytes resolved_ip = 2;\n}\n\nmessage RevokeInterceptRequest {\n  // The full intercept ID in the format \"sessionID:interceptName\"\n  // This allows revoking intercepts for any client, not just the caller's own intercepts\n  string intercept_id = 1;\n}\n"
  },
  {
    "path": "rpc/connector/connector_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.1\n// - protoc             v6.30.2\n// source: connector/connector.proto\n\npackage connector\n\nimport (\n\tcontext \"context\"\n\tcommon \"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\tdaemon \"github.com/telepresenceio/telepresence/rpc/v2/daemon\"\n\tmanager \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tConnector_Version_FullMethodName                 = \"/telepresence.connector.Connector/Version\"\n\tConnector_RootDaemonVersion_FullMethodName       = \"/telepresence.connector.Connector/RootDaemonVersion\"\n\tConnector_TrafficManagerVersion_FullMethodName   = \"/telepresence.connector.Connector/TrafficManagerVersion\"\n\tConnector_AgentImageFQN_FullMethodName           = \"/telepresence.connector.Connector/AgentImageFQN\"\n\tConnector_GetIntercept_FullMethodName            = \"/telepresence.connector.Connector/GetIntercept\"\n\tConnector_Connect_FullMethodName                 = \"/telepresence.connector.Connector/Connect\"\n\tConnector_Disconnect_FullMethodName              = \"/telepresence.connector.Connector/Disconnect\"\n\tConnector_GetClusterSubnets_FullMethodName       = \"/telepresence.connector.Connector/GetClusterSubnets\"\n\tConnector_Status_FullMethodName                  = \"/telepresence.connector.Connector/Status\"\n\tConnector_CanIntercept_FullMethodName            = \"/telepresence.connector.Connector/CanIntercept\"\n\tConnector_Ingest_FullMethodName                  = \"/telepresence.connector.Connector/Ingest\"\n\tConnector_GetIngest_FullMethodName               = \"/telepresence.connector.Connector/GetIngest\"\n\tConnector_LeaveIngest_FullMethodName             = \"/telepresence.connector.Connector/LeaveIngest\"\n\tConnector_CreateIntercept_FullMethodName         = \"/telepresence.connector.Connector/CreateIntercept\"\n\tConnector_RemoveIntercept_FullMethodName         = \"/telepresence.connector.Connector/RemoveIntercept\"\n\tConnector_RevokeIntercept_FullMethodName         = \"/telepresence.connector.Connector/RevokeIntercept\"\n\tConnector_Uninstall_FullMethodName               = \"/telepresence.connector.Connector/Uninstall\"\n\tConnector_List_FullMethodName                    = \"/telepresence.connector.Connector/List\"\n\tConnector_WatchWorkloads_FullMethodName          = \"/telepresence.connector.Connector/WatchWorkloads\"\n\tConnector_SetLogLevel_FullMethodName             = \"/telepresence.connector.Connector/SetLogLevel\"\n\tConnector_Quit_FullMethodName                    = \"/telepresence.connector.Connector/Quit\"\n\tConnector_GatherLogs_FullMethodName              = \"/telepresence.connector.Connector/GatherLogs\"\n\tConnector_AddInterceptor_FullMethodName          = \"/telepresence.connector.Connector/AddInterceptor\"\n\tConnector_RemoveInterceptor_FullMethodName       = \"/telepresence.connector.Connector/RemoveInterceptor\"\n\tConnector_GetNamespaces_FullMethodName           = \"/telepresence.connector.Connector/GetNamespaces\"\n\tConnector_GetKnownWorkloadKinds_FullMethodName   = \"/telepresence.connector.Connector/GetKnownWorkloadKinds\"\n\tConnector_RemoteMountAvailability_FullMethodName = \"/telepresence.connector.Connector/RemoteMountAvailability\"\n\tConnector_GetConfig_FullMethodName               = \"/telepresence.connector.Connector/GetConfig\"\n\tConnector_SetDNSExcludes_FullMethodName          = \"/telepresence.connector.Connector/SetDNSExcludes\"\n\tConnector_SetDNSMappings_FullMethodName          = \"/telepresence.connector.Connector/SetDNSMappings\"\n\tConnector_GetAgentConfig_FullMethodName          = \"/telepresence.connector.Connector/GetAgentConfig\"\n\tConnector_ResolveSyntheticIP_FullMethodName      = \"/telepresence.connector.Connector/ResolveSyntheticIP\"\n\tConnector_LookupIP_FullMethodName                = \"/telepresence.connector.Connector/LookupIP\"\n\tConnector_ResolvePort_FullMethodName             = \"/telepresence.connector.Connector/ResolvePort\"\n\tConnector_RerouteLocalPort_FullMethodName        = \"/telepresence.connector.Connector/RerouteLocalPort\"\n\tConnector_RerouteRemotePort_FullMethodName       = \"/telepresence.connector.Connector/RerouteRemotePort\"\n)\n\n// ConnectorClient is the client API for Connector service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\n//\n// The Connector service is responsible for connecting to the traffic manager\n// and manage intercepts. It can only run when a Daemon is running.\ntype ConnectorClient interface {\n\t// Returns version information from the Connector\n\tVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*common.VersionInfo, error)\n\t// Returns version information from the Root Daemon\n\tRootDaemonVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*common.VersionInfo, error)\n\t// Returns version information from the Traffic Manager\n\tTrafficManagerVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*common.VersionInfo, error)\n\t// Returns the fully qualified image name of the traffic-agent that the agent-injector is configured to inject.\n\tAgentImageFQN(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*manager.AgentImageFQN, error)\n\t// GetIntercept gets info from intercept name\n\tGetIntercept(ctx context.Context, in *manager.GetInterceptRequest, opts ...grpc.CallOption) (*manager.InterceptInfo, error)\n\t// Connects to the cluster and connects the laptop's network (via\n\t// the daemon process) to the cluster's network.  A result code of\n\t// UNSPECIFIED indicates that the connection was successfully\n\t// initiated; if already connected, then either ALREADY_CONNECTED or\n\t// MUST_RESTART is returned, based on whether the current connection\n\t// is in agreement with the ConnectionRequest.\n\tConnect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (*ConnectInfo, error)\n\t// Disconnects the cluster\n\tDisconnect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// GetClusterSubnets gets the outbound info that has been set on daemon\n\tGetClusterSubnets(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClusterSubnets, error)\n\t// Status returns the status of the current connection or DISCONNECTED\n\t// if no connection has been established.\n\tStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConnectInfo, error)\n\t// Queries the connector whether it is possible to create the given intercept\n\t// and returns an error if it's not.\n\tCanIntercept(ctx context.Context, in *CreateInterceptRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Starts an Ingest session.\n\tIngest(ctx context.Context, in *IngestRequest, opts ...grpc.CallOption) (*IngestInfo, error)\n\t// Get info about an ongoing Ingest.\n\tGetIngest(ctx context.Context, in *IngestIdentifier, opts ...grpc.CallOption) (*IngestInfo, error)\n\t// Ends an Ingest session.\n\tLeaveIngest(ctx context.Context, in *IngestIdentifier, opts ...grpc.CallOption) (*IngestInfo, error)\n\t// Adds an intercept to a workload.  Requires having already called\n\t// Connect.\n\tCreateIntercept(ctx context.Context, in *CreateInterceptRequest, opts ...grpc.CallOption) (*manager.InterceptInfo, error)\n\t// Deactivates and removes an existent workload intercept.\n\t// Requires having already called Connect.\n\tRemoveIntercept(ctx context.Context, in *manager.RemoveInterceptRequest2, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Revokes an intercept by intercept ID. This is an administrative operation that\n\t// requires RBAC permissions to modify the \"traffic-manager\" configmap.\n\tRevokeIntercept(ctx context.Context, in *RevokeInterceptRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Uninstalls traffic-agents from the cluster.\n\t// Requires having already called Connect.\n\tUninstall(ctx context.Context, in *UninstallRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Returns a list of workloads and their current intercept status.\n\t// Requires having already called Connect.\n\tList(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*WorkloadInfoSnapshot, error)\n\t// Watch all workloads in the mapped namespaces\n\tWatchWorkloads(ctx context.Context, in *WatchWorkloadsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WorkloadInfoSnapshot], error)\n\t// SetLogLevel will temporarily change the log-level of the traffic-manager, traffic-agent, and user and root daemons.\n\tSetLogLevel(ctx context.Context, in *LogLevelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Quits (terminates) the connector process.\n\tQuit(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*daemon.QuitResponse, error)\n\t// GatherLogs will acquire logs for the various Telepresence components in kubernetes\n\t// (pending the request) and return them to the caller\n\tGatherLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (*LogsResponse, error)\n\t// AddInterceptor tells the connector that a given process is serving a specific\n\t// intercept. The connector must kill this process when the intercept ends\n\tAddInterceptor(ctx context.Context, in *Interceptor, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// RemoveInterceptor removes a previously added interceptor\n\tRemoveInterceptor(ctx context.Context, in *Interceptor, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// GetNamespaces gets the mapped namespaces with an optional prefix\n\tGetNamespaces(ctx context.Context, in *GetNamespacesRequest, opts ...grpc.CallOption) (*GetNamespacesResponse, error)\n\t// GetKnownWorkloadKinds returns the known workload kinds\n\tGetKnownWorkloadKinds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*manager.KnownWorkloadKinds, error)\n\t// RemoteMountAvailability checks if remote mounts are possible using the given\n\t// mount type and returns an error if its not.\n\tRemoteMountAvailability(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// GetConfig returns the current configuration\n\tGetConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClientConfig, error)\n\t// SetDNSExcludes sets the excludes field of DNSConfig.\n\tSetDNSExcludes(ctx context.Context, in *daemon.SetDNSExcludesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// SetDNSMappings sets the Mappings field of DNSConfig.\n\tSetDNSMappings(ctx context.Context, in *daemon.SetDNSMappingsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// GetAgentConfig returns the agent configuration for a specific workload.\n\tGetAgentConfig(ctx context.Context, in *manager.AgentConfigRequest, opts ...grpc.CallOption) (*manager.AgentConfigResponse, error)\n\t// ResolveSyntheticIP resolves a synthetic IP into a name and optionally\n\t// the first IP for that name when the daemon performs a DNS lookup.\n\tResolveSyntheticIP(ctx context.Context, in *ResolveSyntheticRequest, opts ...grpc.CallOption) (*ResolveSyntheticResponse, error)\n\t// LookupIP resolves the given name using the Telepresence DNS server\n\tLookupIP(ctx context.Context, in *daemon.LookupIPRequest, opts ...grpc.CallOption) (*daemon.LookupIPResponse, error)\n\t// ResolvePort resolves an host:port string from into a netip.AddrPort\n\tResolvePort(ctx context.Context, in *daemon.ResolvePortRequest, opts ...grpc.CallOption) (*daemon.ResolvePortResponse, error)\n\t// RerouteLocalPort reroutes a port on localhost to a netip.AddrPort.\n\tRerouteLocalPort(ctx context.Context, in *daemon.ReroutePortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// RerouteRemotePort makes a netip.AddrPort available on a new port on the same address.\n\tRerouteRemotePort(ctx context.Context, in *daemon.ReroutePortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n}\n\ntype connectorClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewConnectorClient(cc grpc.ClientConnInterface) ConnectorClient {\n\treturn &connectorClient{cc}\n}\n\nfunc (c *connectorClient) Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*common.VersionInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(common.VersionInfo)\n\terr := c.cc.Invoke(ctx, Connector_Version_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) RootDaemonVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*common.VersionInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(common.VersionInfo)\n\terr := c.cc.Invoke(ctx, Connector_RootDaemonVersion_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) TrafficManagerVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*common.VersionInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(common.VersionInfo)\n\terr := c.cc.Invoke(ctx, Connector_TrafficManagerVersion_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) AgentImageFQN(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*manager.AgentImageFQN, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(manager.AgentImageFQN)\n\terr := c.cc.Invoke(ctx, Connector_AgentImageFQN_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) GetIntercept(ctx context.Context, in *manager.GetInterceptRequest, opts ...grpc.CallOption) (*manager.InterceptInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(manager.InterceptInfo)\n\terr := c.cc.Invoke(ctx, Connector_GetIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) Connect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (*ConnectInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ConnectInfo)\n\terr := c.cc.Invoke(ctx, Connector_Connect_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) Disconnect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_Disconnect_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) GetClusterSubnets(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClusterSubnets, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ClusterSubnets)\n\terr := c.cc.Invoke(ctx, Connector_GetClusterSubnets_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) Status(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ConnectInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ConnectInfo)\n\terr := c.cc.Invoke(ctx, Connector_Status_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) CanIntercept(ctx context.Context, in *CreateInterceptRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_CanIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) Ingest(ctx context.Context, in *IngestRequest, opts ...grpc.CallOption) (*IngestInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(IngestInfo)\n\terr := c.cc.Invoke(ctx, Connector_Ingest_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) GetIngest(ctx context.Context, in *IngestIdentifier, opts ...grpc.CallOption) (*IngestInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(IngestInfo)\n\terr := c.cc.Invoke(ctx, Connector_GetIngest_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) LeaveIngest(ctx context.Context, in *IngestIdentifier, opts ...grpc.CallOption) (*IngestInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(IngestInfo)\n\terr := c.cc.Invoke(ctx, Connector_LeaveIngest_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) CreateIntercept(ctx context.Context, in *CreateInterceptRequest, opts ...grpc.CallOption) (*manager.InterceptInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(manager.InterceptInfo)\n\terr := c.cc.Invoke(ctx, Connector_CreateIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) RemoveIntercept(ctx context.Context, in *manager.RemoveInterceptRequest2, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_RemoveIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) RevokeIntercept(ctx context.Context, in *RevokeInterceptRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_RevokeIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) Uninstall(ctx context.Context, in *UninstallRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_Uninstall_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*WorkloadInfoSnapshot, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(WorkloadInfoSnapshot)\n\terr := c.cc.Invoke(ctx, Connector_List_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) WatchWorkloads(ctx context.Context, in *WatchWorkloadsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WorkloadInfoSnapshot], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Connector_ServiceDesc.Streams[0], Connector_WatchWorkloads_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[WatchWorkloadsRequest, WorkloadInfoSnapshot]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Connector_WatchWorkloadsClient = grpc.ServerStreamingClient[WorkloadInfoSnapshot]\n\nfunc (c *connectorClient) SetLogLevel(ctx context.Context, in *LogLevelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_SetLogLevel_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) Quit(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*daemon.QuitResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(daemon.QuitResponse)\n\terr := c.cc.Invoke(ctx, Connector_Quit_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) GatherLogs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (*LogsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(LogsResponse)\n\terr := c.cc.Invoke(ctx, Connector_GatherLogs_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) AddInterceptor(ctx context.Context, in *Interceptor, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_AddInterceptor_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) RemoveInterceptor(ctx context.Context, in *Interceptor, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_RemoveInterceptor_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) GetNamespaces(ctx context.Context, in *GetNamespacesRequest, opts ...grpc.CallOption) (*GetNamespacesResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetNamespacesResponse)\n\terr := c.cc.Invoke(ctx, Connector_GetNamespaces_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) GetKnownWorkloadKinds(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*manager.KnownWorkloadKinds, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(manager.KnownWorkloadKinds)\n\terr := c.cc.Invoke(ctx, Connector_GetKnownWorkloadKinds_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) RemoteMountAvailability(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_RemoteMountAvailability_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) GetConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*ClientConfig, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ClientConfig)\n\terr := c.cc.Invoke(ctx, Connector_GetConfig_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) SetDNSExcludes(ctx context.Context, in *daemon.SetDNSExcludesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_SetDNSExcludes_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) SetDNSMappings(ctx context.Context, in *daemon.SetDNSMappingsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_SetDNSMappings_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) GetAgentConfig(ctx context.Context, in *manager.AgentConfigRequest, opts ...grpc.CallOption) (*manager.AgentConfigResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(manager.AgentConfigResponse)\n\terr := c.cc.Invoke(ctx, Connector_GetAgentConfig_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) ResolveSyntheticIP(ctx context.Context, in *ResolveSyntheticRequest, opts ...grpc.CallOption) (*ResolveSyntheticResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ResolveSyntheticResponse)\n\terr := c.cc.Invoke(ctx, Connector_ResolveSyntheticIP_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) LookupIP(ctx context.Context, in *daemon.LookupIPRequest, opts ...grpc.CallOption) (*daemon.LookupIPResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(daemon.LookupIPResponse)\n\terr := c.cc.Invoke(ctx, Connector_LookupIP_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) ResolvePort(ctx context.Context, in *daemon.ResolvePortRequest, opts ...grpc.CallOption) (*daemon.ResolvePortResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(daemon.ResolvePortResponse)\n\terr := c.cc.Invoke(ctx, Connector_ResolvePort_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) RerouteLocalPort(ctx context.Context, in *daemon.ReroutePortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_RerouteLocalPort_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *connectorClient) RerouteRemotePort(ctx context.Context, in *daemon.ReroutePortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Connector_RerouteRemotePort_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ConnectorServer is the server API for Connector service.\n// All implementations must embed UnimplementedConnectorServer\n// for forward compatibility.\n//\n// The Connector service is responsible for connecting to the traffic manager\n// and manage intercepts. It can only run when a Daemon is running.\ntype ConnectorServer interface {\n\t// Returns version information from the Connector\n\tVersion(context.Context, *emptypb.Empty) (*common.VersionInfo, error)\n\t// Returns version information from the Root Daemon\n\tRootDaemonVersion(context.Context, *emptypb.Empty) (*common.VersionInfo, error)\n\t// Returns version information from the Traffic Manager\n\tTrafficManagerVersion(context.Context, *emptypb.Empty) (*common.VersionInfo, error)\n\t// Returns the fully qualified image name of the traffic-agent that the agent-injector is configured to inject.\n\tAgentImageFQN(context.Context, *emptypb.Empty) (*manager.AgentImageFQN, error)\n\t// GetIntercept gets info from intercept name\n\tGetIntercept(context.Context, *manager.GetInterceptRequest) (*manager.InterceptInfo, error)\n\t// Connects to the cluster and connects the laptop's network (via\n\t// the daemon process) to the cluster's network.  A result code of\n\t// UNSPECIFIED indicates that the connection was successfully\n\t// initiated; if already connected, then either ALREADY_CONNECTED or\n\t// MUST_RESTART is returned, based on whether the current connection\n\t// is in agreement with the ConnectionRequest.\n\tConnect(context.Context, *ConnectRequest) (*ConnectInfo, error)\n\t// Disconnects the cluster\n\tDisconnect(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\t// GetClusterSubnets gets the outbound info that has been set on daemon\n\tGetClusterSubnets(context.Context, *emptypb.Empty) (*ClusterSubnets, error)\n\t// Status returns the status of the current connection or DISCONNECTED\n\t// if no connection has been established.\n\tStatus(context.Context, *emptypb.Empty) (*ConnectInfo, error)\n\t// Queries the connector whether it is possible to create the given intercept\n\t// and returns an error if it's not.\n\tCanIntercept(context.Context, *CreateInterceptRequest) (*emptypb.Empty, error)\n\t// Starts an Ingest session.\n\tIngest(context.Context, *IngestRequest) (*IngestInfo, error)\n\t// Get info about an ongoing Ingest.\n\tGetIngest(context.Context, *IngestIdentifier) (*IngestInfo, error)\n\t// Ends an Ingest session.\n\tLeaveIngest(context.Context, *IngestIdentifier) (*IngestInfo, error)\n\t// Adds an intercept to a workload.  Requires having already called\n\t// Connect.\n\tCreateIntercept(context.Context, *CreateInterceptRequest) (*manager.InterceptInfo, error)\n\t// Deactivates and removes an existent workload intercept.\n\t// Requires having already called Connect.\n\tRemoveIntercept(context.Context, *manager.RemoveInterceptRequest2) (*emptypb.Empty, error)\n\t// Revokes an intercept by intercept ID. This is an administrative operation that\n\t// requires RBAC permissions to modify the \"traffic-manager\" configmap.\n\tRevokeIntercept(context.Context, *RevokeInterceptRequest) (*emptypb.Empty, error)\n\t// Uninstalls traffic-agents from the cluster.\n\t// Requires having already called Connect.\n\tUninstall(context.Context, *UninstallRequest) (*emptypb.Empty, error)\n\t// Returns a list of workloads and their current intercept status.\n\t// Requires having already called Connect.\n\tList(context.Context, *ListRequest) (*WorkloadInfoSnapshot, error)\n\t// Watch all workloads in the mapped namespaces\n\tWatchWorkloads(*WatchWorkloadsRequest, grpc.ServerStreamingServer[WorkloadInfoSnapshot]) error\n\t// SetLogLevel will temporarily change the log-level of the traffic-manager, traffic-agent, and user and root daemons.\n\tSetLogLevel(context.Context, *LogLevelRequest) (*emptypb.Empty, error)\n\t// Quits (terminates) the connector process.\n\tQuit(context.Context, *emptypb.Empty) (*daemon.QuitResponse, error)\n\t// GatherLogs will acquire logs for the various Telepresence components in kubernetes\n\t// (pending the request) and return them to the caller\n\tGatherLogs(context.Context, *LogsRequest) (*LogsResponse, error)\n\t// AddInterceptor tells the connector that a given process is serving a specific\n\t// intercept. The connector must kill this process when the intercept ends\n\tAddInterceptor(context.Context, *Interceptor) (*emptypb.Empty, error)\n\t// RemoveInterceptor removes a previously added interceptor\n\tRemoveInterceptor(context.Context, *Interceptor) (*emptypb.Empty, error)\n\t// GetNamespaces gets the mapped namespaces with an optional prefix\n\tGetNamespaces(context.Context, *GetNamespacesRequest) (*GetNamespacesResponse, error)\n\t// GetKnownWorkloadKinds returns the known workload kinds\n\tGetKnownWorkloadKinds(context.Context, *emptypb.Empty) (*manager.KnownWorkloadKinds, error)\n\t// RemoteMountAvailability checks if remote mounts are possible using the given\n\t// mount type and returns an error if its not.\n\tRemoteMountAvailability(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\t// GetConfig returns the current configuration\n\tGetConfig(context.Context, *emptypb.Empty) (*ClientConfig, error)\n\t// SetDNSExcludes sets the excludes field of DNSConfig.\n\tSetDNSExcludes(context.Context, *daemon.SetDNSExcludesRequest) (*emptypb.Empty, error)\n\t// SetDNSMappings sets the Mappings field of DNSConfig.\n\tSetDNSMappings(context.Context, *daemon.SetDNSMappingsRequest) (*emptypb.Empty, error)\n\t// GetAgentConfig returns the agent configuration for a specific workload.\n\tGetAgentConfig(context.Context, *manager.AgentConfigRequest) (*manager.AgentConfigResponse, error)\n\t// ResolveSyntheticIP resolves a synthetic IP into a name and optionally\n\t// the first IP for that name when the daemon performs a DNS lookup.\n\tResolveSyntheticIP(context.Context, *ResolveSyntheticRequest) (*ResolveSyntheticResponse, error)\n\t// LookupIP resolves the given name using the Telepresence DNS server\n\tLookupIP(context.Context, *daemon.LookupIPRequest) (*daemon.LookupIPResponse, error)\n\t// ResolvePort resolves an host:port string from into a netip.AddrPort\n\tResolvePort(context.Context, *daemon.ResolvePortRequest) (*daemon.ResolvePortResponse, error)\n\t// RerouteLocalPort reroutes a port on localhost to a netip.AddrPort.\n\tRerouteLocalPort(context.Context, *daemon.ReroutePortRequest) (*emptypb.Empty, error)\n\t// RerouteRemotePort makes a netip.AddrPort available on a new port on the same address.\n\tRerouteRemotePort(context.Context, *daemon.ReroutePortRequest) (*emptypb.Empty, error)\n\tmustEmbedUnimplementedConnectorServer()\n}\n\n// UnimplementedConnectorServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedConnectorServer struct{}\n\nfunc (UnimplementedConnectorServer) Version(context.Context, *emptypb.Empty) (*common.VersionInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Version not implemented\")\n}\nfunc (UnimplementedConnectorServer) RootDaemonVersion(context.Context, *emptypb.Empty) (*common.VersionInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RootDaemonVersion not implemented\")\n}\nfunc (UnimplementedConnectorServer) TrafficManagerVersion(context.Context, *emptypb.Empty) (*common.VersionInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method TrafficManagerVersion not implemented\")\n}\nfunc (UnimplementedConnectorServer) AgentImageFQN(context.Context, *emptypb.Empty) (*manager.AgentImageFQN, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method AgentImageFQN not implemented\")\n}\nfunc (UnimplementedConnectorServer) GetIntercept(context.Context, *manager.GetInterceptRequest) (*manager.InterceptInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetIntercept not implemented\")\n}\nfunc (UnimplementedConnectorServer) Connect(context.Context, *ConnectRequest) (*ConnectInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Connect not implemented\")\n}\nfunc (UnimplementedConnectorServer) Disconnect(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Disconnect not implemented\")\n}\nfunc (UnimplementedConnectorServer) GetClusterSubnets(context.Context, *emptypb.Empty) (*ClusterSubnets, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetClusterSubnets not implemented\")\n}\nfunc (UnimplementedConnectorServer) Status(context.Context, *emptypb.Empty) (*ConnectInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Status not implemented\")\n}\nfunc (UnimplementedConnectorServer) CanIntercept(context.Context, *CreateInterceptRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method CanIntercept not implemented\")\n}\nfunc (UnimplementedConnectorServer) Ingest(context.Context, *IngestRequest) (*IngestInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Ingest not implemented\")\n}\nfunc (UnimplementedConnectorServer) GetIngest(context.Context, *IngestIdentifier) (*IngestInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetIngest not implemented\")\n}\nfunc (UnimplementedConnectorServer) LeaveIngest(context.Context, *IngestIdentifier) (*IngestInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method LeaveIngest not implemented\")\n}\nfunc (UnimplementedConnectorServer) CreateIntercept(context.Context, *CreateInterceptRequest) (*manager.InterceptInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method CreateIntercept not implemented\")\n}\nfunc (UnimplementedConnectorServer) RemoveIntercept(context.Context, *manager.RemoveInterceptRequest2) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RemoveIntercept not implemented\")\n}\nfunc (UnimplementedConnectorServer) RevokeIntercept(context.Context, *RevokeInterceptRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RevokeIntercept not implemented\")\n}\nfunc (UnimplementedConnectorServer) Uninstall(context.Context, *UninstallRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Uninstall not implemented\")\n}\nfunc (UnimplementedConnectorServer) List(context.Context, *ListRequest) (*WorkloadInfoSnapshot, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method List not implemented\")\n}\nfunc (UnimplementedConnectorServer) WatchWorkloads(*WatchWorkloadsRequest, grpc.ServerStreamingServer[WorkloadInfoSnapshot]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchWorkloads not implemented\")\n}\nfunc (UnimplementedConnectorServer) SetLogLevel(context.Context, *LogLevelRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetLogLevel not implemented\")\n}\nfunc (UnimplementedConnectorServer) Quit(context.Context, *emptypb.Empty) (*daemon.QuitResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Quit not implemented\")\n}\nfunc (UnimplementedConnectorServer) GatherLogs(context.Context, *LogsRequest) (*LogsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GatherLogs not implemented\")\n}\nfunc (UnimplementedConnectorServer) AddInterceptor(context.Context, *Interceptor) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method AddInterceptor not implemented\")\n}\nfunc (UnimplementedConnectorServer) RemoveInterceptor(context.Context, *Interceptor) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RemoveInterceptor not implemented\")\n}\nfunc (UnimplementedConnectorServer) GetNamespaces(context.Context, *GetNamespacesRequest) (*GetNamespacesResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetNamespaces not implemented\")\n}\nfunc (UnimplementedConnectorServer) GetKnownWorkloadKinds(context.Context, *emptypb.Empty) (*manager.KnownWorkloadKinds, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetKnownWorkloadKinds not implemented\")\n}\nfunc (UnimplementedConnectorServer) RemoteMountAvailability(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RemoteMountAvailability not implemented\")\n}\nfunc (UnimplementedConnectorServer) GetConfig(context.Context, *emptypb.Empty) (*ClientConfig, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetConfig not implemented\")\n}\nfunc (UnimplementedConnectorServer) SetDNSExcludes(context.Context, *daemon.SetDNSExcludesRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetDNSExcludes not implemented\")\n}\nfunc (UnimplementedConnectorServer) SetDNSMappings(context.Context, *daemon.SetDNSMappingsRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetDNSMappings not implemented\")\n}\nfunc (UnimplementedConnectorServer) GetAgentConfig(context.Context, *manager.AgentConfigRequest) (*manager.AgentConfigResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetAgentConfig not implemented\")\n}\nfunc (UnimplementedConnectorServer) ResolveSyntheticIP(context.Context, *ResolveSyntheticRequest) (*ResolveSyntheticResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ResolveSyntheticIP not implemented\")\n}\nfunc (UnimplementedConnectorServer) LookupIP(context.Context, *daemon.LookupIPRequest) (*daemon.LookupIPResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method LookupIP not implemented\")\n}\nfunc (UnimplementedConnectorServer) ResolvePort(context.Context, *daemon.ResolvePortRequest) (*daemon.ResolvePortResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ResolvePort not implemented\")\n}\nfunc (UnimplementedConnectorServer) RerouteLocalPort(context.Context, *daemon.ReroutePortRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RerouteLocalPort not implemented\")\n}\nfunc (UnimplementedConnectorServer) RerouteRemotePort(context.Context, *daemon.ReroutePortRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RerouteRemotePort not implemented\")\n}\nfunc (UnimplementedConnectorServer) mustEmbedUnimplementedConnectorServer() {}\nfunc (UnimplementedConnectorServer) testEmbeddedByValue()                   {}\n\n// UnsafeConnectorServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ConnectorServer will\n// result in compilation errors.\ntype UnsafeConnectorServer interface {\n\tmustEmbedUnimplementedConnectorServer()\n}\n\nfunc RegisterConnectorServer(s grpc.ServiceRegistrar, srv ConnectorServer) {\n\t// If the following call panics, it indicates UnimplementedConnectorServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&Connector_ServiceDesc, srv)\n}\n\nfunc _Connector_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).Version(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_Version_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).Version(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_RootDaemonVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).RootDaemonVersion(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_RootDaemonVersion_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).RootDaemonVersion(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_TrafficManagerVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).TrafficManagerVersion(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_TrafficManagerVersion_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).TrafficManagerVersion(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_AgentImageFQN_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).AgentImageFQN(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_AgentImageFQN_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).AgentImageFQN(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_GetIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(manager.GetInterceptRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).GetIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_GetIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).GetIntercept(ctx, req.(*manager.GetInterceptRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_Connect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ConnectRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).Connect(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_Connect_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).Connect(ctx, req.(*ConnectRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_Disconnect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).Disconnect(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_Disconnect_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).Disconnect(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_GetClusterSubnets_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).GetClusterSubnets(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_GetClusterSubnets_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).GetClusterSubnets(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).Status(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_Status_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).Status(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_CanIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateInterceptRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).CanIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_CanIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).CanIntercept(ctx, req.(*CreateInterceptRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_Ingest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(IngestRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).Ingest(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_Ingest_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).Ingest(ctx, req.(*IngestRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_GetIngest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(IngestIdentifier)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).GetIngest(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_GetIngest_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).GetIngest(ctx, req.(*IngestIdentifier))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_LeaveIngest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(IngestIdentifier)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).LeaveIngest(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_LeaveIngest_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).LeaveIngest(ctx, req.(*IngestIdentifier))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_CreateIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateInterceptRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).CreateIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_CreateIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).CreateIntercept(ctx, req.(*CreateInterceptRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_RemoveIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(manager.RemoveInterceptRequest2)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).RemoveIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_RemoveIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).RemoveIntercept(ctx, req.(*manager.RemoveInterceptRequest2))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_RevokeIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RevokeInterceptRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).RevokeIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_RevokeIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).RevokeIntercept(ctx, req.(*RevokeInterceptRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_Uninstall_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UninstallRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).Uninstall(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_Uninstall_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).Uninstall(ctx, req.(*UninstallRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).List(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_List_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).List(ctx, req.(*ListRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_WatchWorkloads_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(WatchWorkloadsRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ConnectorServer).WatchWorkloads(m, &grpc.GenericServerStream[WatchWorkloadsRequest, WorkloadInfoSnapshot]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Connector_WatchWorkloadsServer = grpc.ServerStreamingServer[WorkloadInfoSnapshot]\n\nfunc _Connector_SetLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LogLevelRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).SetLogLevel(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_SetLogLevel_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).SetLogLevel(ctx, req.(*LogLevelRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_Quit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).Quit(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_Quit_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).Quit(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_GatherLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LogsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).GatherLogs(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_GatherLogs_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).GatherLogs(ctx, req.(*LogsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_AddInterceptor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Interceptor)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).AddInterceptor(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_AddInterceptor_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).AddInterceptor(ctx, req.(*Interceptor))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_RemoveInterceptor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Interceptor)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).RemoveInterceptor(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_RemoveInterceptor_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).RemoveInterceptor(ctx, req.(*Interceptor))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_GetNamespaces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetNamespacesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).GetNamespaces(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_GetNamespaces_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).GetNamespaces(ctx, req.(*GetNamespacesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_GetKnownWorkloadKinds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).GetKnownWorkloadKinds(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_GetKnownWorkloadKinds_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).GetKnownWorkloadKinds(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_RemoteMountAvailability_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).RemoteMountAvailability(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_RemoteMountAvailability_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).RemoteMountAvailability(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_GetConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).GetConfig(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_GetConfig_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).GetConfig(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_SetDNSExcludes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(daemon.SetDNSExcludesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).SetDNSExcludes(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_SetDNSExcludes_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).SetDNSExcludes(ctx, req.(*daemon.SetDNSExcludesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_SetDNSMappings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(daemon.SetDNSMappingsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).SetDNSMappings(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_SetDNSMappings_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).SetDNSMappings(ctx, req.(*daemon.SetDNSMappingsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_GetAgentConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(manager.AgentConfigRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).GetAgentConfig(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_GetAgentConfig_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).GetAgentConfig(ctx, req.(*manager.AgentConfigRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_ResolveSyntheticIP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ResolveSyntheticRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).ResolveSyntheticIP(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_ResolveSyntheticIP_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).ResolveSyntheticIP(ctx, req.(*ResolveSyntheticRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_LookupIP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(daemon.LookupIPRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).LookupIP(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_LookupIP_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).LookupIP(ctx, req.(*daemon.LookupIPRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_ResolvePort_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(daemon.ResolvePortRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).ResolvePort(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_ResolvePort_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).ResolvePort(ctx, req.(*daemon.ResolvePortRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_RerouteLocalPort_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(daemon.ReroutePortRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).RerouteLocalPort(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_RerouteLocalPort_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).RerouteLocalPort(ctx, req.(*daemon.ReroutePortRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Connector_RerouteRemotePort_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(daemon.ReroutePortRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ConnectorServer).RerouteRemotePort(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Connector_RerouteRemotePort_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ConnectorServer).RerouteRemotePort(ctx, req.(*daemon.ReroutePortRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// Connector_ServiceDesc is the grpc.ServiceDesc for Connector service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar Connector_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"telepresence.connector.Connector\",\n\tHandlerType: (*ConnectorServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Version\",\n\t\t\tHandler:    _Connector_Version_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RootDaemonVersion\",\n\t\t\tHandler:    _Connector_RootDaemonVersion_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"TrafficManagerVersion\",\n\t\t\tHandler:    _Connector_TrafficManagerVersion_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"AgentImageFQN\",\n\t\t\tHandler:    _Connector_AgentImageFQN_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetIntercept\",\n\t\t\tHandler:    _Connector_GetIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Connect\",\n\t\t\tHandler:    _Connector_Connect_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Disconnect\",\n\t\t\tHandler:    _Connector_Disconnect_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetClusterSubnets\",\n\t\t\tHandler:    _Connector_GetClusterSubnets_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Status\",\n\t\t\tHandler:    _Connector_Status_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CanIntercept\",\n\t\t\tHandler:    _Connector_CanIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Ingest\",\n\t\t\tHandler:    _Connector_Ingest_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetIngest\",\n\t\t\tHandler:    _Connector_GetIngest_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"LeaveIngest\",\n\t\t\tHandler:    _Connector_LeaveIngest_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateIntercept\",\n\t\t\tHandler:    _Connector_CreateIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoveIntercept\",\n\t\t\tHandler:    _Connector_RemoveIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RevokeIntercept\",\n\t\t\tHandler:    _Connector_RevokeIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Uninstall\",\n\t\t\tHandler:    _Connector_Uninstall_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"List\",\n\t\t\tHandler:    _Connector_List_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetLogLevel\",\n\t\t\tHandler:    _Connector_SetLogLevel_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Quit\",\n\t\t\tHandler:    _Connector_Quit_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GatherLogs\",\n\t\t\tHandler:    _Connector_GatherLogs_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"AddInterceptor\",\n\t\t\tHandler:    _Connector_AddInterceptor_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoveInterceptor\",\n\t\t\tHandler:    _Connector_RemoveInterceptor_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetNamespaces\",\n\t\t\tHandler:    _Connector_GetNamespaces_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetKnownWorkloadKinds\",\n\t\t\tHandler:    _Connector_GetKnownWorkloadKinds_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoteMountAvailability\",\n\t\t\tHandler:    _Connector_RemoteMountAvailability_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetConfig\",\n\t\t\tHandler:    _Connector_GetConfig_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetDNSExcludes\",\n\t\t\tHandler:    _Connector_SetDNSExcludes_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetDNSMappings\",\n\t\t\tHandler:    _Connector_SetDNSMappings_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetAgentConfig\",\n\t\t\tHandler:    _Connector_GetAgentConfig_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ResolveSyntheticIP\",\n\t\t\tHandler:    _Connector_ResolveSyntheticIP_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"LookupIP\",\n\t\t\tHandler:    _Connector_LookupIP_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ResolvePort\",\n\t\t\tHandler:    _Connector_ResolvePort_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RerouteLocalPort\",\n\t\t\tHandler:    _Connector_RerouteLocalPort_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RerouteRemotePort\",\n\t\t\tHandler:    _Connector_RerouteRemotePort_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"WatchWorkloads\",\n\t\t\tHandler:       _Connector_WatchWorkloads_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"connector/connector.proto\",\n}\n"
  },
  {
    "path": "rpc/daemon/daemon.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.30.2\n// source: daemon/daemon.proto\n\npackage daemon\n\nimport (\n\tcommon \"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\tmanager \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\tdurationpb \"google.golang.org/protobuf/types/known/durationpb\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype DaemonStatus struct {\n\tstate          protoimpl.MessageState `protogen:\"open.v1\"`\n\tManaged        bool                   `protobuf:\"varint,2,opt,name=managed,proto3\" json:\"managed,omitempty\"`\n\tOutboundConfig *NetworkConfig         `protobuf:\"bytes,4,opt,name=outbound_config,json=outboundConfig,proto3\" json:\"outbound_config,omitempty\"`\n\tVersion        *common.VersionInfo    `protobuf:\"bytes,5,opt,name=version,proto3\" json:\"version,omitempty\"`\n\tunknownFields  protoimpl.UnknownFields\n\tsizeCache      protoimpl.SizeCache\n}\n\nfunc (x *DaemonStatus) Reset() {\n\t*x = DaemonStatus{}\n\tmi := &file_daemon_daemon_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DaemonStatus) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DaemonStatus) ProtoMessage() {}\n\nfunc (x *DaemonStatus) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DaemonStatus.ProtoReflect.Descriptor instead.\nfunc (*DaemonStatus) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *DaemonStatus) GetManaged() bool {\n\tif x != nil {\n\t\treturn x.Managed\n\t}\n\treturn false\n}\n\nfunc (x *DaemonStatus) GetOutboundConfig() *NetworkConfig {\n\tif x != nil {\n\t\treturn x.OutboundConfig\n\t}\n\treturn nil\n}\n\nfunc (x *DaemonStatus) GetVersion() *common.VersionInfo {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn nil\n}\n\ntype Domains struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tDomains       []string               `protobuf:\"bytes,1,rep,name=domains,proto3\" json:\"domains,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Domains) Reset() {\n\t*x = Domains{}\n\tmi := &file_daemon_daemon_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Domains) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Domains) ProtoMessage() {}\n\nfunc (x *Domains) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Domains.ProtoReflect.Descriptor instead.\nfunc (*Domains) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *Domains) GetDomains() []string {\n\tif x != nil {\n\t\treturn x.Domains\n\t}\n\treturn nil\n}\n\ntype DNSMapping struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tAliasFor      string                 `protobuf:\"bytes,2,opt,name=alias_for,json=aliasFor,proto3\" json:\"alias_for,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DNSMapping) Reset() {\n\t*x = DNSMapping{}\n\tmi := &file_daemon_daemon_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DNSMapping) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DNSMapping) ProtoMessage() {}\n\nfunc (x *DNSMapping) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DNSMapping.ProtoReflect.Descriptor instead.\nfunc (*DNSMapping) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *DNSMapping) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *DNSMapping) GetAliasFor() string {\n\tif x != nil {\n\t\treturn x.AliasFor\n\t}\n\treturn \"\"\n}\n\n// DNS configuration for the local DNS resolver\ntype DNSConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// local_address is the address and port of the local DNS server.\n\t// In netip.AddrPort binary form.\n\tLocalAddresses [][]byte `protobuf:\"bytes,1,rep,name=local_addresses,json=localAddresses,proto3\" json:\"local_addresses,omitempty\"`\n\t// vif_address is the address and port that the DNS server uses on the Telepresence VIF. Only used by Linux systems.\n\t// In netip.AddrPort binary form.\n\tVifAddress []byte `protobuf:\"bytes,2,opt,name=vif_address,json=vifAddress,proto3\" json:\"vif_address,omitempty\"`\n\t// Suffixes to exclude\n\tExcludeSuffixes []string `protobuf:\"bytes,3,rep,name=exclude_suffixes,json=excludeSuffixes,proto3\" json:\"exclude_suffixes,omitempty\"`\n\t// Suffixes to include. Has higher prio than the excludes\n\tIncludeSuffixes []string `protobuf:\"bytes,4,rep,name=include_suffixes,json=includeSuffixes,proto3\" json:\"include_suffixes,omitempty\"`\n\t// Exclude are a list of hostname that the DNS resolver will not resolve even if they exist.\n\tExcludes []string `protobuf:\"bytes,8,rep,name=excludes,proto3\" json:\"excludes,omitempty\"`\n\t// DNSMapping contains a hostname and its associated alias. When requesting the name, the intended behavior is\n\t// to resolve the alias instead.\n\tMappings []*DNSMapping `protobuf:\"bytes,9,rep,name=mappings,proto3\" json:\"mappings,omitempty\"`\n\t// The maximum time wait for a cluster side host lookup.\n\tLookupTimeout *durationpb.Duration `protobuf:\"bytes,6,opt,name=lookup_timeout,json=lookupTimeout,proto3\" json:\"lookup_timeout,omitempty\"`\n\t// Perform a recursion check on first connect.\n\tRecursionCheck bool `protobuf:\"varint,5,opt,name=recursion_check,json=recursionCheck,proto3\" json:\"recursion_check,omitempty\"`\n\t// If set, this error indicates why DNS is not working.\n\tError         string `protobuf:\"bytes,7,opt,name=error,proto3\" json:\"error,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DNSConfig) Reset() {\n\t*x = DNSConfig{}\n\tmi := &file_daemon_daemon_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DNSConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DNSConfig) ProtoMessage() {}\n\nfunc (x *DNSConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DNSConfig.ProtoReflect.Descriptor instead.\nfunc (*DNSConfig) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *DNSConfig) GetLocalAddresses() [][]byte {\n\tif x != nil {\n\t\treturn x.LocalAddresses\n\t}\n\treturn nil\n}\n\nfunc (x *DNSConfig) GetVifAddress() []byte {\n\tif x != nil {\n\t\treturn x.VifAddress\n\t}\n\treturn nil\n}\n\nfunc (x *DNSConfig) GetExcludeSuffixes() []string {\n\tif x != nil {\n\t\treturn x.ExcludeSuffixes\n\t}\n\treturn nil\n}\n\nfunc (x *DNSConfig) GetIncludeSuffixes() []string {\n\tif x != nil {\n\t\treturn x.IncludeSuffixes\n\t}\n\treturn nil\n}\n\nfunc (x *DNSConfig) GetExcludes() []string {\n\tif x != nil {\n\t\treturn x.Excludes\n\t}\n\treturn nil\n}\n\nfunc (x *DNSConfig) GetMappings() []*DNSMapping {\n\tif x != nil {\n\t\treturn x.Mappings\n\t}\n\treturn nil\n}\n\nfunc (x *DNSConfig) GetLookupTimeout() *durationpb.Duration {\n\tif x != nil {\n\t\treturn x.LookupTimeout\n\t}\n\treturn nil\n}\n\nfunc (x *DNSConfig) GetRecursionCheck() bool {\n\tif x != nil {\n\t\treturn x.RecursionCheck\n\t}\n\treturn false\n}\n\nfunc (x *DNSConfig) GetError() string {\n\tif x != nil {\n\t\treturn x.Error\n\t}\n\treturn \"\"\n}\n\ntype SubnetViaWorkload struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The remote IP that the DNS resolver translates into a Virtual IP to use locally.\n\tSubnet string `protobuf:\"bytes,1,opt,name=subnet,proto3\" json:\"subnet,omitempty\"`\n\t// The workload that the virtual IP will be routed to.\n\tWorkload      string `protobuf:\"bytes,2,opt,name=workload,proto3\" json:\"workload,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SubnetViaWorkload) Reset() {\n\t*x = SubnetViaWorkload{}\n\tmi := &file_daemon_daemon_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SubnetViaWorkload) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SubnetViaWorkload) ProtoMessage() {}\n\nfunc (x *SubnetViaWorkload) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SubnetViaWorkload.ProtoReflect.Descriptor instead.\nfunc (*SubnetViaWorkload) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *SubnetViaWorkload) GetSubnet() string {\n\tif x != nil {\n\t\treturn x.Subnet\n\t}\n\treturn \"\"\n}\n\nfunc (x *SubnetViaWorkload) GetWorkload() string {\n\tif x != nil {\n\t\treturn x.Workload\n\t}\n\treturn \"\"\n}\n\n// NetworkConfig contains all information that the root daemon needs in order to\n// establish outbound traffic to the cluster.\ntype NetworkConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// session makes it possible for the root daemon to identify itself as the\n\t// same client as the user daemon.\n\tSession *manager.SessionInfo `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\t// Route subnets via given workload using virtual IPs\n\tSubnetViaWorkloads []*SubnetViaWorkload `protobuf:\"bytes,2,rep,name=subnet_via_workloads,json=subnetViaWorkloads,proto3\" json:\"subnet_via_workloads,omitempty\"`\n\t// Port mappings enforced by the TUN-device.\n\tPortMappings []string `protobuf:\"bytes,8,rep,name=port_mappings,json=portMappings,proto3\" json:\"port_mappings,omitempty\"`\n\t// Users home directory\n\tHomeDir string `protobuf:\"bytes,3,opt,name=home_dir,json=homeDir,proto3\" json:\"home_dir,omitempty\"`\n\t// Connection namespace\n\tNamespace string `protobuf:\"bytes,4,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\t// Namespace where the connected traffic-manager is installed.\n\tManagerNamespace string `protobuf:\"bytes,9,opt,name=manager_namespace,json=managerNamespace,proto3\" json:\"manager_namespace,omitempty\"`\n\t// Mapped namespaces\n\tMappedNamespaces []string `protobuf:\"bytes,10,rep,name=mapped_namespaces,json=mappedNamespaces,proto3\" json:\"mapped_namespaces,omitempty\"`\n\t// Kubernetes flags\n\tKubeFlags map[string]string `protobuf:\"bytes,5,rep,name=kube_flags,json=kubeFlags,proto3\" json:\"kube_flags,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// Kubeconfig YAML, if not to be loaded from file.\n\tKubeconfigData []byte `protobuf:\"bytes,6,opt,name=kubeconfig_data,json=kubeconfigData,proto3,oneof\" json:\"kubeconfig_data,omitempty\"`\n\t// The completely merged client Config, unless daemon runs embedded\n\tClientConfig  []byte `protobuf:\"bytes,7,opt,name=client_config,json=clientConfig,proto3,oneof\" json:\"client_config,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *NetworkConfig) Reset() {\n\t*x = NetworkConfig{}\n\tmi := &file_daemon_daemon_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *NetworkConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*NetworkConfig) ProtoMessage() {}\n\nfunc (x *NetworkConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use NetworkConfig.ProtoReflect.Descriptor instead.\nfunc (*NetworkConfig) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *NetworkConfig) GetSession() *manager.SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkConfig) GetSubnetViaWorkloads() []*SubnetViaWorkload {\n\tif x != nil {\n\t\treturn x.SubnetViaWorkloads\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkConfig) GetPortMappings() []string {\n\tif x != nil {\n\t\treturn x.PortMappings\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkConfig) GetHomeDir() string {\n\tif x != nil {\n\t\treturn x.HomeDir\n\t}\n\treturn \"\"\n}\n\nfunc (x *NetworkConfig) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *NetworkConfig) GetManagerNamespace() string {\n\tif x != nil {\n\t\treturn x.ManagerNamespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *NetworkConfig) GetMappedNamespaces() []string {\n\tif x != nil {\n\t\treturn x.MappedNamespaces\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkConfig) GetKubeFlags() map[string]string {\n\tif x != nil {\n\t\treturn x.KubeFlags\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkConfig) GetKubeconfigData() []byte {\n\tif x != nil {\n\t\treturn x.KubeconfigData\n\t}\n\treturn nil\n}\n\nfunc (x *NetworkConfig) GetClientConfig() []byte {\n\tif x != nil {\n\t\treturn x.ClientConfig\n\t}\n\treturn nil\n}\n\ntype SetDNSExcludesRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tExcludes      []string               `protobuf:\"bytes,1,rep,name=excludes,proto3\" json:\"excludes,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SetDNSExcludesRequest) Reset() {\n\t*x = SetDNSExcludesRequest{}\n\tmi := &file_daemon_daemon_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SetDNSExcludesRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SetDNSExcludesRequest) ProtoMessage() {}\n\nfunc (x *SetDNSExcludesRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SetDNSExcludesRequest.ProtoReflect.Descriptor instead.\nfunc (*SetDNSExcludesRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *SetDNSExcludesRequest) GetExcludes() []string {\n\tif x != nil {\n\t\treturn x.Excludes\n\t}\n\treturn nil\n}\n\ntype SetDNSMappingsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tMappings      []*DNSMapping          `protobuf:\"bytes,1,rep,name=mappings,proto3\" json:\"mappings,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *SetDNSMappingsRequest) Reset() {\n\t*x = SetDNSMappingsRequest{}\n\tmi := &file_daemon_daemon_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SetDNSMappingsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SetDNSMappingsRequest) ProtoMessage() {}\n\nfunc (x *SetDNSMappingsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SetDNSMappingsRequest.ProtoReflect.Descriptor instead.\nfunc (*SetDNSMappingsRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *SetDNSMappingsRequest) GetMappings() []*DNSMapping {\n\tif x != nil {\n\t\treturn x.Mappings\n\t}\n\treturn nil\n}\n\ntype WaitForAgentIPRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tIp            []byte                 `protobuf:\"bytes,1,opt,name=ip,proto3\" json:\"ip,omitempty\"`\n\tTimeout       *durationpb.Duration   `protobuf:\"bytes,2,opt,name=timeout,proto3\" json:\"timeout,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WaitForAgentIPRequest) Reset() {\n\t*x = WaitForAgentIPRequest{}\n\tmi := &file_daemon_daemon_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WaitForAgentIPRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WaitForAgentIPRequest) ProtoMessage() {}\n\nfunc (x *WaitForAgentIPRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WaitForAgentIPRequest.ProtoReflect.Descriptor instead.\nfunc (*WaitForAgentIPRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *WaitForAgentIPRequest) GetIp() []byte {\n\tif x != nil {\n\t\treturn x.Ip\n\t}\n\treturn nil\n}\n\nfunc (x *WaitForAgentIPRequest) GetTimeout() *durationpb.Duration {\n\tif x != nil {\n\t\treturn x.Timeout\n\t}\n\treturn nil\n}\n\ntype WaitForAgentIPResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The local IP of the agent (might be virtual)\n\tLocalIp       []byte `protobuf:\"bytes,1,opt,name=local_ip,json=localIp,proto3\" json:\"local_ip,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WaitForAgentIPResponse) Reset() {\n\t*x = WaitForAgentIPResponse{}\n\tmi := &file_daemon_daemon_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WaitForAgentIPResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WaitForAgentIPResponse) ProtoMessage() {}\n\nfunc (x *WaitForAgentIPResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WaitForAgentIPResponse.ProtoReflect.Descriptor instead.\nfunc (*WaitForAgentIPResponse) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *WaitForAgentIPResponse) GetLocalIp() []byte {\n\tif x != nil {\n\t\treturn x.LocalIp\n\t}\n\treturn nil\n}\n\ntype LookupIPRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LookupIPRequest) Reset() {\n\t*x = LookupIPRequest{}\n\tmi := &file_daemon_daemon_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LookupIPRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LookupIPRequest) ProtoMessage() {}\n\nfunc (x *LookupIPRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LookupIPRequest.ProtoReflect.Descriptor instead.\nfunc (*LookupIPRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *LookupIPRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype LookupIPResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// IP in netip.Addr binary form\n\tIp            []byte `protobuf:\"bytes,1,opt,name=ip,proto3\" json:\"ip,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LookupIPResponse) Reset() {\n\t*x = LookupIPResponse{}\n\tmi := &file_daemon_daemon_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LookupIPResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LookupIPResponse) ProtoMessage() {}\n\nfunc (x *LookupIPResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LookupIPResponse.ProtoReflect.Descriptor instead.\nfunc (*LookupIPResponse) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *LookupIPResponse) GetIp() []byte {\n\tif x != nil {\n\t\treturn x.Ip\n\t}\n\treturn nil\n}\n\ntype Environment struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEnv           map[string]string      `protobuf:\"bytes,1,rep,name=env,proto3\" json:\"env,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Environment) Reset() {\n\t*x = Environment{}\n\tmi := &file_daemon_daemon_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Environment) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Environment) ProtoMessage() {}\n\nfunc (x *Environment) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Environment.ProtoReflect.Descriptor instead.\nfunc (*Environment) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *Environment) GetEnv() map[string]string {\n\tif x != nil {\n\t\treturn x.Env\n\t}\n\treturn nil\n}\n\ntype ResolvePortRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// host is the name or IP-address of the targeted host.\n\tHost string `protobuf:\"bytes,1,opt,name=host,proto3\" json:\"host,omitempty\"`\n\t// port can be symbolic if the host is the name of a service, and can be suffixed with /{tcp|udp}.\n\tPort          string `protobuf:\"bytes,2,opt,name=port,proto3\" json:\"port,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ResolvePortRequest) Reset() {\n\t*x = ResolvePortRequest{}\n\tmi := &file_daemon_daemon_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ResolvePortRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ResolvePortRequest) ProtoMessage() {}\n\nfunc (x *ResolvePortRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[13]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ResolvePortRequest.ProtoReflect.Descriptor instead.\nfunc (*ResolvePortRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *ResolvePortRequest) GetHost() string {\n\tif x != nil {\n\t\treturn x.Host\n\t}\n\treturn \"\"\n}\n\nfunc (x *ResolvePortRequest) GetPort() string {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn \"\"\n}\n\ntype ResolvePortResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// host_port is a iputil.ProtoAddrPort in binary format\n\tHostPort      []byte `protobuf:\"bytes,1,opt,name=host_port,json=hostPort,proto3\" json:\"host_port,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ResolvePortResponse) Reset() {\n\t*x = ResolvePortResponse{}\n\tmi := &file_daemon_daemon_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ResolvePortResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ResolvePortResponse) ProtoMessage() {}\n\nfunc (x *ResolvePortResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[14]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ResolvePortResponse.ProtoReflect.Descriptor instead.\nfunc (*ResolvePortResponse) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *ResolvePortResponse) GetHostPort() []byte {\n\tif x != nil {\n\t\treturn x.HostPort\n\t}\n\treturn nil\n}\n\ntype ReroutePortRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// host_port is a iputil.ProtoAddrPort in binary format\n\tDstHostPort []byte `protobuf:\"bytes,1,opt,name=dst_host_port,json=dstHostPort,proto3\" json:\"dst_host_port,omitempty\"`\n\t// port is will be rerouted to the host_port.\n\tSrcPort       uint32 `protobuf:\"varint,2,opt,name=src_port,json=srcPort,proto3\" json:\"src_port,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ReroutePortRequest) Reset() {\n\t*x = ReroutePortRequest{}\n\tmi := &file_daemon_daemon_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReroutePortRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReroutePortRequest) ProtoMessage() {}\n\nfunc (x *ReroutePortRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[15]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReroutePortRequest.ProtoReflect.Descriptor instead.\nfunc (*ReroutePortRequest) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *ReroutePortRequest) GetDstHostPort() []byte {\n\tif x != nil {\n\t\treturn x.DstHostPort\n\t}\n\treturn nil\n}\n\nfunc (x *ReroutePortRequest) GetSrcPort() uint32 {\n\tif x != nil {\n\t\treturn x.SrcPort\n\t}\n\treturn 0\n}\n\ntype QuitResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Set to true when the root daemon runs as a service.\n\tRootDaemonWillContinue bool `protobuf:\"varint,1,opt,name=root_daemon_will_continue,json=rootDaemonWillContinue,proto3\" json:\"root_daemon_will_continue,omitempty\"`\n\tunknownFields          protoimpl.UnknownFields\n\tsizeCache              protoimpl.SizeCache\n}\n\nfunc (x *QuitResponse) Reset() {\n\t*x = QuitResponse{}\n\tmi := &file_daemon_daemon_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *QuitResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*QuitResponse) ProtoMessage() {}\n\nfunc (x *QuitResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[16]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use QuitResponse.ProtoReflect.Descriptor instead.\nfunc (*QuitResponse) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *QuitResponse) GetRootDaemonWillContinue() bool {\n\tif x != nil {\n\t\treturn x.RootDaemonWillContinue\n\t}\n\treturn false\n}\n\ntype Activity struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tActivity      *timestamppb.Timestamp `protobuf:\"bytes,1,opt,name=activity,proto3\" json:\"activity,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Activity) Reset() {\n\t*x = Activity{}\n\tmi := &file_daemon_daemon_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Activity) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Activity) ProtoMessage() {}\n\nfunc (x *Activity) ProtoReflect() protoreflect.Message {\n\tmi := &file_daemon_daemon_proto_msgTypes[17]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Activity.ProtoReflect.Descriptor instead.\nfunc (*Activity) Descriptor() ([]byte, []int) {\n\treturn file_daemon_daemon_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *Activity) GetActivity() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.Activity\n\t}\n\treturn nil\n}\n\nvar File_daemon_daemon_proto protoreflect.FileDescriptor\n\nconst file_daemon_daemon_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x13daemon/daemon.proto\\x12\\x13telepresence.daemon\\x1a\\x14common/version.proto\\x1a\\x1egoogle/protobuf/duration.proto\\x1a\\x1bgoogle/protobuf/empty.proto\\x1a\\x1fgoogle/protobuf/timestamp.proto\\x1a\\x15manager/manager.proto\\\"\\xb7\\x01\\n\" +\n\t\"\\fDaemonStatus\\x12\\x18\\n\" +\n\t\"\\amanaged\\x18\\x02 \\x01(\\bR\\amanaged\\x12K\\n\" +\n\t\"\\x0foutbound_config\\x18\\x04 \\x01(\\v2\\\".telepresence.daemon.NetworkConfigR\\x0eoutboundConfig\\x12:\\n\" +\n\t\"\\aversion\\x18\\x05 \\x01(\\v2 .telepresence.common.VersionInfoR\\aversionJ\\x04\\b\\x03\\x10\\x04\\\"#\\n\" +\n\t\"\\aDomains\\x12\\x18\\n\" +\n\t\"\\adomains\\x18\\x01 \\x03(\\tR\\adomains\\\"=\\n\" +\n\t\"\\n\" +\n\t\"DNSMapping\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x1b\\n\" +\n\t\"\\talias_for\\x18\\x02 \\x01(\\tR\\baliasFor\\\"\\x85\\x03\\n\" +\n\t\"\\tDNSConfig\\x12'\\n\" +\n\t\"\\x0flocal_addresses\\x18\\x01 \\x03(\\fR\\x0elocalAddresses\\x12\\x1f\\n\" +\n\t\"\\vvif_address\\x18\\x02 \\x01(\\fR\\n\" +\n\t\"vifAddress\\x12)\\n\" +\n\t\"\\x10exclude_suffixes\\x18\\x03 \\x03(\\tR\\x0fexcludeSuffixes\\x12)\\n\" +\n\t\"\\x10include_suffixes\\x18\\x04 \\x03(\\tR\\x0fincludeSuffixes\\x12\\x1a\\n\" +\n\t\"\\bexcludes\\x18\\b \\x03(\\tR\\bexcludes\\x12;\\n\" +\n\t\"\\bmappings\\x18\\t \\x03(\\v2\\x1f.telepresence.daemon.DNSMappingR\\bmappings\\x12@\\n\" +\n\t\"\\x0elookup_timeout\\x18\\x06 \\x01(\\v2\\x19.google.protobuf.DurationR\\rlookupTimeout\\x12'\\n\" +\n\t\"\\x0frecursion_check\\x18\\x05 \\x01(\\bR\\x0erecursionCheck\\x12\\x14\\n\" +\n\t\"\\x05error\\x18\\a \\x01(\\tR\\x05error\\\"G\\n\" +\n\t\"\\x11SubnetViaWorkload\\x12\\x16\\n\" +\n\t\"\\x06subnet\\x18\\x01 \\x01(\\tR\\x06subnet\\x12\\x1a\\n\" +\n\t\"\\bworkload\\x18\\x02 \\x01(\\tR\\bworkload\\\"\\xec\\x04\\n\" +\n\t\"\\rNetworkConfig\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12X\\n\" +\n\t\"\\x14subnet_via_workloads\\x18\\x02 \\x03(\\v2&.telepresence.daemon.SubnetViaWorkloadR\\x12subnetViaWorkloads\\x12#\\n\" +\n\t\"\\rport_mappings\\x18\\b \\x03(\\tR\\fportMappings\\x12\\x19\\n\" +\n\t\"\\bhome_dir\\x18\\x03 \\x01(\\tR\\ahomeDir\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x04 \\x01(\\tR\\tnamespace\\x12+\\n\" +\n\t\"\\x11manager_namespace\\x18\\t \\x01(\\tR\\x10managerNamespace\\x12+\\n\" +\n\t\"\\x11mapped_namespaces\\x18\\n\" +\n\t\" \\x03(\\tR\\x10mappedNamespaces\\x12P\\n\" +\n\t\"\\n\" +\n\t\"kube_flags\\x18\\x05 \\x03(\\v21.telepresence.daemon.NetworkConfig.KubeFlagsEntryR\\tkubeFlags\\x12,\\n\" +\n\t\"\\x0fkubeconfig_data\\x18\\x06 \\x01(\\fH\\x00R\\x0ekubeconfigData\\x88\\x01\\x01\\x12(\\n\" +\n\t\"\\rclient_config\\x18\\a \\x01(\\fH\\x01R\\fclientConfig\\x88\\x01\\x01\\x1a<\\n\" +\n\t\"\\x0eKubeFlagsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01B\\x12\\n\" +\n\t\"\\x10_kubeconfig_dataB\\x10\\n\" +\n\t\"\\x0e_client_config\\\"3\\n\" +\n\t\"\\x15SetDNSExcludesRequest\\x12\\x1a\\n\" +\n\t\"\\bexcludes\\x18\\x01 \\x03(\\tR\\bexcludes\\\"T\\n\" +\n\t\"\\x15SetDNSMappingsRequest\\x12;\\n\" +\n\t\"\\bmappings\\x18\\x01 \\x03(\\v2\\x1f.telepresence.daemon.DNSMappingR\\bmappings\\\"\\\\\\n\" +\n\t\"\\x15WaitForAgentIPRequest\\x12\\x0e\\n\" +\n\t\"\\x02ip\\x18\\x01 \\x01(\\fR\\x02ip\\x123\\n\" +\n\t\"\\atimeout\\x18\\x02 \\x01(\\v2\\x19.google.protobuf.DurationR\\atimeout\\\"3\\n\" +\n\t\"\\x16WaitForAgentIPResponse\\x12\\x19\\n\" +\n\t\"\\blocal_ip\\x18\\x01 \\x01(\\fR\\alocalIp\\\"%\\n\" +\n\t\"\\x0fLookupIPRequest\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\\"\\\"\\n\" +\n\t\"\\x10LookupIPResponse\\x12\\x0e\\n\" +\n\t\"\\x02ip\\x18\\x01 \\x01(\\fR\\x02ip\\\"\\x82\\x01\\n\" +\n\t\"\\vEnvironment\\x12;\\n\" +\n\t\"\\x03env\\x18\\x01 \\x03(\\v2).telepresence.daemon.Environment.EnvEntryR\\x03env\\x1a6\\n\" +\n\t\"\\bEnvEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\\"<\\n\" +\n\t\"\\x12ResolvePortRequest\\x12\\x12\\n\" +\n\t\"\\x04host\\x18\\x01 \\x01(\\tR\\x04host\\x12\\x12\\n\" +\n\t\"\\x04port\\x18\\x02 \\x01(\\tR\\x04port\\\"2\\n\" +\n\t\"\\x13ResolvePortResponse\\x12\\x1b\\n\" +\n\t\"\\thost_port\\x18\\x01 \\x01(\\fR\\bhostPort\\\"S\\n\" +\n\t\"\\x12ReroutePortRequest\\x12\\\"\\n\" +\n\t\"\\rdst_host_port\\x18\\x01 \\x01(\\fR\\vdstHostPort\\x12\\x19\\n\" +\n\t\"\\bsrc_port\\x18\\x02 \\x01(\\rR\\asrcPort\\\"I\\n\" +\n\t\"\\fQuitResponse\\x129\\n\" +\n\t\"\\x19root_daemon_will_continue\\x18\\x01 \\x01(\\bR\\x16rootDaemonWillContinue\\\"B\\n\" +\n\t\"\\bActivity\\x126\\n\" +\n\t\"\\bactivity\\x18\\x01 \\x01(\\v2\\x1a.google.protobuf.TimestampR\\bactivity2\\xdf\\n\" +\n\t\"\\n\" +\n\t\"\\x06Daemon\\x12C\\n\" +\n\t\"\\aVersion\\x12\\x16.google.protobuf.Empty\\x1a .telepresence.common.VersionInfo\\x12C\\n\" +\n\t\"\\x06Status\\x12\\x16.google.protobuf.Empty\\x1a!.telepresence.daemon.DaemonStatus\\x12A\\n\" +\n\t\"\\x04Quit\\x12\\x16.google.protobuf.Empty\\x1a!.telepresence.daemon.QuitResponse\\x12P\\n\" +\n\t\"\\aConnect\\x12\\\".telepresence.daemon.NetworkConfig\\x1a!.telepresence.daemon.DaemonStatus\\x12<\\n\" +\n\t\"\\n\" +\n\t\"Disconnect\\x12\\x16.google.protobuf.Empty\\x1a\\x16.google.protobuf.Empty\\x12N\\n\" +\n\t\"\\x10GetNetworkConfig\\x12\\x16.google.protobuf.Empty\\x1a\\\".telepresence.daemon.NetworkConfig\\x12M\\n\" +\n\t\"\\x15SetDNSTopLevelDomains\\x12\\x1c.telepresence.daemon.Domains\\x1a\\x16.google.protobuf.Empty\\x12T\\n\" +\n\t\"\\x0eSetDNSExcludes\\x12*.telepresence.daemon.SetDNSExcludesRequest\\x1a\\x16.google.protobuf.Empty\\x12T\\n\" +\n\t\"\\x0eSetDNSMappings\\x12*.telepresence.daemon.SetDNSMappingsRequest\\x1a\\x16.google.protobuf.Empty\\x12L\\n\" +\n\t\"\\vSetLogLevel\\x12%.telepresence.manager.LogLevelRequest\\x1a\\x16.google.protobuf.Empty\\x12U\\n\" +\n\t\"\\x0fTranslateEnvIPs\\x12 .telepresence.daemon.Environment\\x1a .telepresence.daemon.Environment\\x12@\\n\" +\n\t\"\\x0eWaitForNetwork\\x12\\x16.google.protobuf.Empty\\x1a\\x16.google.protobuf.Empty\\x12i\\n\" +\n\t\"\\x0eWaitForAgentIP\\x12*.telepresence.daemon.WaitForAgentIPRequest\\x1a+.telepresence.daemon.WaitForAgentIPResponse\\x12W\\n\" +\n\t\"\\bLookupIP\\x12$.telepresence.daemon.LookupIPRequest\\x1a%.telepresence.daemon.LookupIPResponse\\x12`\\n\" +\n\t\"\\vResolvePort\\x12'.telepresence.daemon.ResolvePortRequest\\x1a(.telepresence.daemon.ResolvePortResponse\\x12T\\n\" +\n\t\"\\x11RerouteRemotePort\\x12'.telepresence.daemon.ReroutePortRequest\\x1a\\x16.google.protobuf.Empty\\x12J\\n\" +\n\t\"\\x0fActivityWatcher\\x12\\x16.google.protobuf.Empty\\x1a\\x1d.telepresence.daemon.Activity0\\x01B6Z4github.com/telepresenceio/telepresence/rpc/v2/daemonb\\x06proto3\"\n\nvar (\n\tfile_daemon_daemon_proto_rawDescOnce sync.Once\n\tfile_daemon_daemon_proto_rawDescData []byte\n)\n\nfunc file_daemon_daemon_proto_rawDescGZIP() []byte {\n\tfile_daemon_daemon_proto_rawDescOnce.Do(func() {\n\t\tfile_daemon_daemon_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_daemon_daemon_proto_rawDesc), len(file_daemon_daemon_proto_rawDesc)))\n\t})\n\treturn file_daemon_daemon_proto_rawDescData\n}\n\nvar file_daemon_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 20)\nvar file_daemon_daemon_proto_goTypes = []any{\n\t(*DaemonStatus)(nil),            // 0: telepresence.daemon.DaemonStatus\n\t(*Domains)(nil),                 // 1: telepresence.daemon.Domains\n\t(*DNSMapping)(nil),              // 2: telepresence.daemon.DNSMapping\n\t(*DNSConfig)(nil),               // 3: telepresence.daemon.DNSConfig\n\t(*SubnetViaWorkload)(nil),       // 4: telepresence.daemon.SubnetViaWorkload\n\t(*NetworkConfig)(nil),           // 5: telepresence.daemon.NetworkConfig\n\t(*SetDNSExcludesRequest)(nil),   // 6: telepresence.daemon.SetDNSExcludesRequest\n\t(*SetDNSMappingsRequest)(nil),   // 7: telepresence.daemon.SetDNSMappingsRequest\n\t(*WaitForAgentIPRequest)(nil),   // 8: telepresence.daemon.WaitForAgentIPRequest\n\t(*WaitForAgentIPResponse)(nil),  // 9: telepresence.daemon.WaitForAgentIPResponse\n\t(*LookupIPRequest)(nil),         // 10: telepresence.daemon.LookupIPRequest\n\t(*LookupIPResponse)(nil),        // 11: telepresence.daemon.LookupIPResponse\n\t(*Environment)(nil),             // 12: telepresence.daemon.Environment\n\t(*ResolvePortRequest)(nil),      // 13: telepresence.daemon.ResolvePortRequest\n\t(*ResolvePortResponse)(nil),     // 14: telepresence.daemon.ResolvePortResponse\n\t(*ReroutePortRequest)(nil),      // 15: telepresence.daemon.ReroutePortRequest\n\t(*QuitResponse)(nil),            // 16: telepresence.daemon.QuitResponse\n\t(*Activity)(nil),                // 17: telepresence.daemon.Activity\n\tnil,                             // 18: telepresence.daemon.NetworkConfig.KubeFlagsEntry\n\tnil,                             // 19: telepresence.daemon.Environment.EnvEntry\n\t(*common.VersionInfo)(nil),      // 20: telepresence.common.VersionInfo\n\t(*durationpb.Duration)(nil),     // 21: google.protobuf.Duration\n\t(*manager.SessionInfo)(nil),     // 22: telepresence.manager.SessionInfo\n\t(*timestamppb.Timestamp)(nil),   // 23: google.protobuf.Timestamp\n\t(*emptypb.Empty)(nil),           // 24: google.protobuf.Empty\n\t(*manager.LogLevelRequest)(nil), // 25: telepresence.manager.LogLevelRequest\n}\nvar file_daemon_daemon_proto_depIdxs = []int32{\n\t5,  // 0: telepresence.daemon.DaemonStatus.outbound_config:type_name -> telepresence.daemon.NetworkConfig\n\t20, // 1: telepresence.daemon.DaemonStatus.version:type_name -> telepresence.common.VersionInfo\n\t2,  // 2: telepresence.daemon.DNSConfig.mappings:type_name -> telepresence.daemon.DNSMapping\n\t21, // 3: telepresence.daemon.DNSConfig.lookup_timeout:type_name -> google.protobuf.Duration\n\t22, // 4: telepresence.daemon.NetworkConfig.session:type_name -> telepresence.manager.SessionInfo\n\t4,  // 5: telepresence.daemon.NetworkConfig.subnet_via_workloads:type_name -> telepresence.daemon.SubnetViaWorkload\n\t18, // 6: telepresence.daemon.NetworkConfig.kube_flags:type_name -> telepresence.daemon.NetworkConfig.KubeFlagsEntry\n\t2,  // 7: telepresence.daemon.SetDNSMappingsRequest.mappings:type_name -> telepresence.daemon.DNSMapping\n\t21, // 8: telepresence.daemon.WaitForAgentIPRequest.timeout:type_name -> google.protobuf.Duration\n\t19, // 9: telepresence.daemon.Environment.env:type_name -> telepresence.daemon.Environment.EnvEntry\n\t23, // 10: telepresence.daemon.Activity.activity:type_name -> google.protobuf.Timestamp\n\t24, // 11: telepresence.daemon.Daemon.Version:input_type -> google.protobuf.Empty\n\t24, // 12: telepresence.daemon.Daemon.Status:input_type -> google.protobuf.Empty\n\t24, // 13: telepresence.daemon.Daemon.Quit:input_type -> google.protobuf.Empty\n\t5,  // 14: telepresence.daemon.Daemon.Connect:input_type -> telepresence.daemon.NetworkConfig\n\t24, // 15: telepresence.daemon.Daemon.Disconnect:input_type -> google.protobuf.Empty\n\t24, // 16: telepresence.daemon.Daemon.GetNetworkConfig:input_type -> google.protobuf.Empty\n\t1,  // 17: telepresence.daemon.Daemon.SetDNSTopLevelDomains:input_type -> telepresence.daemon.Domains\n\t6,  // 18: telepresence.daemon.Daemon.SetDNSExcludes:input_type -> telepresence.daemon.SetDNSExcludesRequest\n\t7,  // 19: telepresence.daemon.Daemon.SetDNSMappings:input_type -> telepresence.daemon.SetDNSMappingsRequest\n\t25, // 20: telepresence.daemon.Daemon.SetLogLevel:input_type -> telepresence.manager.LogLevelRequest\n\t12, // 21: telepresence.daemon.Daemon.TranslateEnvIPs:input_type -> telepresence.daemon.Environment\n\t24, // 22: telepresence.daemon.Daemon.WaitForNetwork:input_type -> google.protobuf.Empty\n\t8,  // 23: telepresence.daemon.Daemon.WaitForAgentIP:input_type -> telepresence.daemon.WaitForAgentIPRequest\n\t10, // 24: telepresence.daemon.Daemon.LookupIP:input_type -> telepresence.daemon.LookupIPRequest\n\t13, // 25: telepresence.daemon.Daemon.ResolvePort:input_type -> telepresence.daemon.ResolvePortRequest\n\t15, // 26: telepresence.daemon.Daemon.RerouteRemotePort:input_type -> telepresence.daemon.ReroutePortRequest\n\t24, // 27: telepresence.daemon.Daemon.ActivityWatcher:input_type -> google.protobuf.Empty\n\t20, // 28: telepresence.daemon.Daemon.Version:output_type -> telepresence.common.VersionInfo\n\t0,  // 29: telepresence.daemon.Daemon.Status:output_type -> telepresence.daemon.DaemonStatus\n\t16, // 30: telepresence.daemon.Daemon.Quit:output_type -> telepresence.daemon.QuitResponse\n\t0,  // 31: telepresence.daemon.Daemon.Connect:output_type -> telepresence.daemon.DaemonStatus\n\t24, // 32: telepresence.daemon.Daemon.Disconnect:output_type -> google.protobuf.Empty\n\t5,  // 33: telepresence.daemon.Daemon.GetNetworkConfig:output_type -> telepresence.daemon.NetworkConfig\n\t24, // 34: telepresence.daemon.Daemon.SetDNSTopLevelDomains:output_type -> google.protobuf.Empty\n\t24, // 35: telepresence.daemon.Daemon.SetDNSExcludes:output_type -> google.protobuf.Empty\n\t24, // 36: telepresence.daemon.Daemon.SetDNSMappings:output_type -> google.protobuf.Empty\n\t24, // 37: telepresence.daemon.Daemon.SetLogLevel:output_type -> google.protobuf.Empty\n\t12, // 38: telepresence.daemon.Daemon.TranslateEnvIPs:output_type -> telepresence.daemon.Environment\n\t24, // 39: telepresence.daemon.Daemon.WaitForNetwork:output_type -> google.protobuf.Empty\n\t9,  // 40: telepresence.daemon.Daemon.WaitForAgentIP:output_type -> telepresence.daemon.WaitForAgentIPResponse\n\t11, // 41: telepresence.daemon.Daemon.LookupIP:output_type -> telepresence.daemon.LookupIPResponse\n\t14, // 42: telepresence.daemon.Daemon.ResolvePort:output_type -> telepresence.daemon.ResolvePortResponse\n\t24, // 43: telepresence.daemon.Daemon.RerouteRemotePort:output_type -> google.protobuf.Empty\n\t17, // 44: telepresence.daemon.Daemon.ActivityWatcher:output_type -> telepresence.daemon.Activity\n\t28, // [28:45] is the sub-list for method output_type\n\t11, // [11:28] is the sub-list for method input_type\n\t11, // [11:11] is the sub-list for extension type_name\n\t11, // [11:11] is the sub-list for extension extendee\n\t0,  // [0:11] is the sub-list for field type_name\n}\n\nfunc init() { file_daemon_daemon_proto_init() }\nfunc file_daemon_daemon_proto_init() {\n\tif File_daemon_daemon_proto != nil {\n\t\treturn\n\t}\n\tfile_daemon_daemon_proto_msgTypes[5].OneofWrappers = []any{}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_daemon_proto_rawDesc), len(file_daemon_daemon_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   20,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_daemon_daemon_proto_goTypes,\n\t\tDependencyIndexes: file_daemon_daemon_proto_depIdxs,\n\t\tMessageInfos:      file_daemon_daemon_proto_msgTypes,\n\t}.Build()\n\tFile_daemon_daemon_proto = out.File\n\tfile_daemon_daemon_proto_goTypes = nil\n\tfile_daemon_daemon_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "rpc/daemon/daemon.proto",
    "content": "syntax = \"proto3\";\npackage telepresence.daemon;\n\nimport \"common/version.proto\";\nimport \"google/protobuf/duration.proto\";\nimport \"google/protobuf/empty.proto\";\nimport \"google/protobuf/timestamp.proto\";\nimport \"manager/manager.proto\";\n\noption go_package = \"github.com/telepresenceio/telepresence/rpc/v2/daemon\";\n\n// The Daemon service is responsible for managing network overrides and also\n// acts as the central point for logging.\nservice Daemon {\n  // Version returns version information from the Daemon\n  rpc Version(google.protobuf.Empty) returns (telepresence.common.VersionInfo);\n\n  // Status returns the current connectivity status\n  rpc Status(google.protobuf.Empty) returns (DaemonStatus);\n\n  // Quit quits (terminates) the service.\n  rpc Quit(google.protobuf.Empty) returns (QuitResponse);\n\n  // Connect creates a new session that provides outbound connectivity to the cluster\n  rpc Connect(NetworkConfig) returns (DaemonStatus);\n\n  // Disconnect disconnects the current session.\n  rpc Disconnect(google.protobuf.Empty) returns (google.protobuf.Empty);\n\n  // GetNetworkConfig returns the current network configuration\n  rpc GetNetworkConfig(google.protobuf.Empty) returns (NetworkConfig);\n\n  // SetDNSTopLevelDomains sets a new search path.\n  rpc SetDNSTopLevelDomains(Domains) returns (google.protobuf.Empty);\n\n  // SetDNSExcludes sets the excludes field of DNSConfig.\n  rpc SetDNSExcludes(SetDNSExcludesRequest) returns (google.protobuf.Empty);\n\n  // SetDNSMappings sets the Mappings field of DNSConfig.\n  rpc SetDNSMappings(SetDNSMappingsRequest) returns (google.protobuf.Empty);\n\n  // SetLogLevel will temporarily set the log-level for the daemon for a duration that is determined b the request.\n  rpc SetLogLevel(manager.LogLevelRequest) returns (google.protobuf.Empty);\n\n  // TranslateEnvIPs translates remote IPs found in the environment to local IPs\n  rpc TranslateEnvIPs(Environment) returns (Environment);\n\n  // WaitForNetwork waits for the network of the currently connected session to become ready.\n  rpc WaitForNetwork(google.protobuf.Empty) returns (google.protobuf.Empty);\n\n  // WaitForAgentIP waits for the network of an intercepted agent to become ready.\n  rpc WaitForAgentIP(WaitForAgentIPRequest) returns (WaitForAgentIPResponse);\n\n  // LookupIP resolves the given name using the Telepresence DNS server\n  rpc LookupIP(LookupIPRequest) returns (LookupIPResponse);\n\n  // ResolvePort resolves an hostName:portID string from into a netip.AddrPort\n  rpc ResolvePort(ResolvePortRequest) returns (ResolvePortResponse);\n\n  // RerouteRemotePort makes a netip.AddrPort available on a new port on the same address.\n  rpc RerouteRemotePort(ReroutePortRequest) returns (google.protobuf.Empty);\n\n  rpc ActivityWatcher(google.protobuf.Empty) returns (stream Activity);\n}\n\nmessage DaemonStatus {\n  bool managed = 2;\n  NetworkConfig outbound_config = 4;\n  telepresence.common.VersionInfo version = 5;\n  reserved 3;\n}\n\nmessage Domains {\n  repeated string domains = 1;\n}\n\nmessage DNSMapping {\n  string name = 1;\n  string alias_for = 2;\n}\n\n// DNS configuration for the local DNS resolver\nmessage DNSConfig {\n  // local_address is the address and port of the local DNS server.\n  // In netip.AddrPort binary form.\n  repeated bytes local_addresses = 1;\n\n  // vif_address is the address and port that the DNS server uses on the Telepresence VIF. Only used by Linux systems.\n  // In netip.AddrPort binary form.\n  bytes vif_address = 2;\n\n  // Suffixes to exclude\n  repeated string exclude_suffixes = 3;\n\n  // Suffixes to include. Has higher prio than the excludes\n  repeated string include_suffixes = 4;\n\n  // Exclude are a list of hostname that the DNS resolver will not resolve even if they exist.\n  repeated string excludes = 8;\n\n  // DNSMapping contains a hostname and its associated alias. When requesting the name, the intended behavior is\n  // to resolve the alias instead.\n  repeated DNSMapping mappings = 9;\n\n  // The maximum time wait for a cluster side host lookup.\n  google.protobuf.Duration lookup_timeout = 6;\n\n  // Perform a recursion check on first connect.\n  bool recursion_check = 5;\n\n  // If set, this error indicates why DNS is not working.\n  string error = 7;\n}\n\nmessage SubnetViaWorkload {\n  // The remote IP that the DNS resolver translates into a Virtual IP to use locally.\n  string subnet = 1;\n\n  // The workload that the virtual IP will be routed to.\n  string workload = 2;\n}\n\n// NetworkConfig contains all information that the root daemon needs in order to\n// establish outbound traffic to the cluster.\nmessage NetworkConfig {\n  // session makes it possible for the root daemon to identify itself as the\n  // same client as the user daemon.\n  manager.SessionInfo session = 1;\n\n  // Route subnets via given workload using virtual IPs\n  repeated SubnetViaWorkload subnet_via_workloads = 2;\n\n  // Port mappings enforced by the TUN-device.\n  repeated string port_mappings = 8;\n\n  // Users home directory\n  string home_dir = 3;\n\n  // Connection namespace\n  string namespace = 4;\n\n  // Namespace where the connected traffic-manager is installed.\n  string manager_namespace = 9;\n\n  // Mapped namespaces\n  repeated string mapped_namespaces = 10;\n\n  // Kubernetes flags\n  map<string, string> kube_flags = 5;\n\n  // Kubeconfig YAML, if not to be loaded from file.\n  optional bytes kubeconfig_data = 6;\n\n  // The completely merged client Config, unless daemon runs embedded\n  optional bytes client_config = 7;\n}\n\nmessage SetDNSExcludesRequest {\n  repeated string excludes = 1;\n}\n\nmessage SetDNSMappingsRequest {\n  repeated DNSMapping mappings = 1;\n}\n\nmessage WaitForAgentIPRequest {\n  bytes ip = 1;\n  google.protobuf.Duration timeout = 2;\n}\n\nmessage WaitForAgentIPResponse {\n  // The local IP of the agent (might be virtual)\n  bytes local_ip = 1;\n}\n\nmessage LookupIPRequest {\n  string name = 1;\n}\n\nmessage LookupIPResponse {\n  // IP in netip.Addr binary form\n  bytes ip = 1;\n}\n\nmessage Environment {\n  map<string, string> env = 1;\n}\n\nmessage ResolvePortRequest {\n  // host is the name or IP-address of the targeted host.\n  string host = 1;\n\n  // port can be symbolic if the host is the name of a service, and can be suffixed with /{tcp|udp}.\n  string port = 2;\n}\n\nmessage ResolvePortResponse {\n  // host_port is a iputil.ProtoAddrPort in binary format\n  bytes host_port = 1;\n}\n\nmessage ReroutePortRequest {\n  // host_port is a iputil.ProtoAddrPort in binary format\n  bytes dst_host_port = 1;\n\n  // port is will be rerouted to the host_port.\n  uint32 src_port = 2;\n}\n\nmessage QuitResponse {\n  // Set to true when the root daemon runs as a service.\n  bool root_daemon_will_continue = 1;\n}\n\nmessage Activity {\n  google.protobuf.Timestamp activity = 1;\n}\n"
  },
  {
    "path": "rpc/daemon/daemon_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.1\n// - protoc             v6.30.2\n// source: daemon/daemon.proto\n\npackage daemon\n\nimport (\n\tcontext \"context\"\n\tcommon \"github.com/telepresenceio/telepresence/rpc/v2/common\"\n\tmanager \"github.com/telepresenceio/telepresence/rpc/v2/manager\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tDaemon_Version_FullMethodName               = \"/telepresence.daemon.Daemon/Version\"\n\tDaemon_Status_FullMethodName                = \"/telepresence.daemon.Daemon/Status\"\n\tDaemon_Quit_FullMethodName                  = \"/telepresence.daemon.Daemon/Quit\"\n\tDaemon_Connect_FullMethodName               = \"/telepresence.daemon.Daemon/Connect\"\n\tDaemon_Disconnect_FullMethodName            = \"/telepresence.daemon.Daemon/Disconnect\"\n\tDaemon_GetNetworkConfig_FullMethodName      = \"/telepresence.daemon.Daemon/GetNetworkConfig\"\n\tDaemon_SetDNSTopLevelDomains_FullMethodName = \"/telepresence.daemon.Daemon/SetDNSTopLevelDomains\"\n\tDaemon_SetDNSExcludes_FullMethodName        = \"/telepresence.daemon.Daemon/SetDNSExcludes\"\n\tDaemon_SetDNSMappings_FullMethodName        = \"/telepresence.daemon.Daemon/SetDNSMappings\"\n\tDaemon_SetLogLevel_FullMethodName           = \"/telepresence.daemon.Daemon/SetLogLevel\"\n\tDaemon_TranslateEnvIPs_FullMethodName       = \"/telepresence.daemon.Daemon/TranslateEnvIPs\"\n\tDaemon_WaitForNetwork_FullMethodName        = \"/telepresence.daemon.Daemon/WaitForNetwork\"\n\tDaemon_WaitForAgentIP_FullMethodName        = \"/telepresence.daemon.Daemon/WaitForAgentIP\"\n\tDaemon_LookupIP_FullMethodName              = \"/telepresence.daemon.Daemon/LookupIP\"\n\tDaemon_ResolvePort_FullMethodName           = \"/telepresence.daemon.Daemon/ResolvePort\"\n\tDaemon_RerouteRemotePort_FullMethodName     = \"/telepresence.daemon.Daemon/RerouteRemotePort\"\n\tDaemon_ActivityWatcher_FullMethodName       = \"/telepresence.daemon.Daemon/ActivityWatcher\"\n)\n\n// DaemonClient is the client API for Daemon service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\n//\n// The Daemon service is responsible for managing network overrides and also\n// acts as the central point for logging.\ntype DaemonClient interface {\n\t// Version returns version information from the Daemon\n\tVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*common.VersionInfo, error)\n\t// Status returns the current connectivity status\n\tStatus(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DaemonStatus, error)\n\t// Quit quits (terminates) the service.\n\tQuit(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*QuitResponse, error)\n\t// Connect creates a new session that provides outbound connectivity to the cluster\n\tConnect(ctx context.Context, in *NetworkConfig, opts ...grpc.CallOption) (*DaemonStatus, error)\n\t// Disconnect disconnects the current session.\n\tDisconnect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// GetNetworkConfig returns the current network configuration\n\tGetNetworkConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*NetworkConfig, error)\n\t// SetDNSTopLevelDomains sets a new search path.\n\tSetDNSTopLevelDomains(ctx context.Context, in *Domains, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// SetDNSExcludes sets the excludes field of DNSConfig.\n\tSetDNSExcludes(ctx context.Context, in *SetDNSExcludesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// SetDNSMappings sets the Mappings field of DNSConfig.\n\tSetDNSMappings(ctx context.Context, in *SetDNSMappingsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// SetLogLevel will temporarily set the log-level for the daemon for a duration that is determined b the request.\n\tSetLogLevel(ctx context.Context, in *manager.LogLevelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// TranslateEnvIPs translates remote IPs found in the environment to local IPs\n\tTranslateEnvIPs(ctx context.Context, in *Environment, opts ...grpc.CallOption) (*Environment, error)\n\t// WaitForNetwork waits for the network of the currently connected session to become ready.\n\tWaitForNetwork(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// WaitForAgentIP waits for the network of an intercepted agent to become ready.\n\tWaitForAgentIP(ctx context.Context, in *WaitForAgentIPRequest, opts ...grpc.CallOption) (*WaitForAgentIPResponse, error)\n\t// LookupIP resolves the given name using the Telepresence DNS server\n\tLookupIP(ctx context.Context, in *LookupIPRequest, opts ...grpc.CallOption) (*LookupIPResponse, error)\n\t// ResolvePort resolves an hostName:portID string from into a netip.AddrPort\n\tResolvePort(ctx context.Context, in *ResolvePortRequest, opts ...grpc.CallOption) (*ResolvePortResponse, error)\n\t// RerouteRemotePort makes a netip.AddrPort available on a new port on the same address.\n\tRerouteRemotePort(ctx context.Context, in *ReroutePortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tActivityWatcher(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Activity], error)\n}\n\ntype daemonClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewDaemonClient(cc grpc.ClientConnInterface) DaemonClient {\n\treturn &daemonClient{cc}\n}\n\nfunc (c *daemonClient) Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*common.VersionInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(common.VersionInfo)\n\terr := c.cc.Invoke(ctx, Daemon_Version_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) Status(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*DaemonStatus, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DaemonStatus)\n\terr := c.cc.Invoke(ctx, Daemon_Status_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) Quit(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*QuitResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(QuitResponse)\n\terr := c.cc.Invoke(ctx, Daemon_Quit_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) Connect(ctx context.Context, in *NetworkConfig, opts ...grpc.CallOption) (*DaemonStatus, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DaemonStatus)\n\terr := c.cc.Invoke(ctx, Daemon_Connect_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) Disconnect(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Daemon_Disconnect_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) GetNetworkConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*NetworkConfig, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(NetworkConfig)\n\terr := c.cc.Invoke(ctx, Daemon_GetNetworkConfig_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) SetDNSTopLevelDomains(ctx context.Context, in *Domains, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Daemon_SetDNSTopLevelDomains_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) SetDNSExcludes(ctx context.Context, in *SetDNSExcludesRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Daemon_SetDNSExcludes_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) SetDNSMappings(ctx context.Context, in *SetDNSMappingsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Daemon_SetDNSMappings_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) SetLogLevel(ctx context.Context, in *manager.LogLevelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Daemon_SetLogLevel_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) TranslateEnvIPs(ctx context.Context, in *Environment, opts ...grpc.CallOption) (*Environment, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(Environment)\n\terr := c.cc.Invoke(ctx, Daemon_TranslateEnvIPs_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) WaitForNetwork(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Daemon_WaitForNetwork_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) WaitForAgentIP(ctx context.Context, in *WaitForAgentIPRequest, opts ...grpc.CallOption) (*WaitForAgentIPResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(WaitForAgentIPResponse)\n\terr := c.cc.Invoke(ctx, Daemon_WaitForAgentIP_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) LookupIP(ctx context.Context, in *LookupIPRequest, opts ...grpc.CallOption) (*LookupIPResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(LookupIPResponse)\n\terr := c.cc.Invoke(ctx, Daemon_LookupIP_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) ResolvePort(ctx context.Context, in *ResolvePortRequest, opts ...grpc.CallOption) (*ResolvePortResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ResolvePortResponse)\n\terr := c.cc.Invoke(ctx, Daemon_ResolvePort_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) RerouteRemotePort(ctx context.Context, in *ReroutePortRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Daemon_RerouteRemotePort_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *daemonClient) ActivityWatcher(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Activity], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Daemon_ServiceDesc.Streams[0], Daemon_ActivityWatcher_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[emptypb.Empty, Activity]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Daemon_ActivityWatcherClient = grpc.ServerStreamingClient[Activity]\n\n// DaemonServer is the server API for Daemon service.\n// All implementations must embed UnimplementedDaemonServer\n// for forward compatibility.\n//\n// The Daemon service is responsible for managing network overrides and also\n// acts as the central point for logging.\ntype DaemonServer interface {\n\t// Version returns version information from the Daemon\n\tVersion(context.Context, *emptypb.Empty) (*common.VersionInfo, error)\n\t// Status returns the current connectivity status\n\tStatus(context.Context, *emptypb.Empty) (*DaemonStatus, error)\n\t// Quit quits (terminates) the service.\n\tQuit(context.Context, *emptypb.Empty) (*QuitResponse, error)\n\t// Connect creates a new session that provides outbound connectivity to the cluster\n\tConnect(context.Context, *NetworkConfig) (*DaemonStatus, error)\n\t// Disconnect disconnects the current session.\n\tDisconnect(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\t// GetNetworkConfig returns the current network configuration\n\tGetNetworkConfig(context.Context, *emptypb.Empty) (*NetworkConfig, error)\n\t// SetDNSTopLevelDomains sets a new search path.\n\tSetDNSTopLevelDomains(context.Context, *Domains) (*emptypb.Empty, error)\n\t// SetDNSExcludes sets the excludes field of DNSConfig.\n\tSetDNSExcludes(context.Context, *SetDNSExcludesRequest) (*emptypb.Empty, error)\n\t// SetDNSMappings sets the Mappings field of DNSConfig.\n\tSetDNSMappings(context.Context, *SetDNSMappingsRequest) (*emptypb.Empty, error)\n\t// SetLogLevel will temporarily set the log-level for the daemon for a duration that is determined b the request.\n\tSetLogLevel(context.Context, *manager.LogLevelRequest) (*emptypb.Empty, error)\n\t// TranslateEnvIPs translates remote IPs found in the environment to local IPs\n\tTranslateEnvIPs(context.Context, *Environment) (*Environment, error)\n\t// WaitForNetwork waits for the network of the currently connected session to become ready.\n\tWaitForNetwork(context.Context, *emptypb.Empty) (*emptypb.Empty, error)\n\t// WaitForAgentIP waits for the network of an intercepted agent to become ready.\n\tWaitForAgentIP(context.Context, *WaitForAgentIPRequest) (*WaitForAgentIPResponse, error)\n\t// LookupIP resolves the given name using the Telepresence DNS server\n\tLookupIP(context.Context, *LookupIPRequest) (*LookupIPResponse, error)\n\t// ResolvePort resolves an hostName:portID string from into a netip.AddrPort\n\tResolvePort(context.Context, *ResolvePortRequest) (*ResolvePortResponse, error)\n\t// RerouteRemotePort makes a netip.AddrPort available on a new port on the same address.\n\tRerouteRemotePort(context.Context, *ReroutePortRequest) (*emptypb.Empty, error)\n\tActivityWatcher(*emptypb.Empty, grpc.ServerStreamingServer[Activity]) error\n\tmustEmbedUnimplementedDaemonServer()\n}\n\n// UnimplementedDaemonServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedDaemonServer struct{}\n\nfunc (UnimplementedDaemonServer) Version(context.Context, *emptypb.Empty) (*common.VersionInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Version not implemented\")\n}\nfunc (UnimplementedDaemonServer) Status(context.Context, *emptypb.Empty) (*DaemonStatus, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Status not implemented\")\n}\nfunc (UnimplementedDaemonServer) Quit(context.Context, *emptypb.Empty) (*QuitResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Quit not implemented\")\n}\nfunc (UnimplementedDaemonServer) Connect(context.Context, *NetworkConfig) (*DaemonStatus, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Connect not implemented\")\n}\nfunc (UnimplementedDaemonServer) Disconnect(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Disconnect not implemented\")\n}\nfunc (UnimplementedDaemonServer) GetNetworkConfig(context.Context, *emptypb.Empty) (*NetworkConfig, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetNetworkConfig not implemented\")\n}\nfunc (UnimplementedDaemonServer) SetDNSTopLevelDomains(context.Context, *Domains) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetDNSTopLevelDomains not implemented\")\n}\nfunc (UnimplementedDaemonServer) SetDNSExcludes(context.Context, *SetDNSExcludesRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetDNSExcludes not implemented\")\n}\nfunc (UnimplementedDaemonServer) SetDNSMappings(context.Context, *SetDNSMappingsRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetDNSMappings not implemented\")\n}\nfunc (UnimplementedDaemonServer) SetLogLevel(context.Context, *manager.LogLevelRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetLogLevel not implemented\")\n}\nfunc (UnimplementedDaemonServer) TranslateEnvIPs(context.Context, *Environment) (*Environment, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method TranslateEnvIPs not implemented\")\n}\nfunc (UnimplementedDaemonServer) WaitForNetwork(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method WaitForNetwork not implemented\")\n}\nfunc (UnimplementedDaemonServer) WaitForAgentIP(context.Context, *WaitForAgentIPRequest) (*WaitForAgentIPResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method WaitForAgentIP not implemented\")\n}\nfunc (UnimplementedDaemonServer) LookupIP(context.Context, *LookupIPRequest) (*LookupIPResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method LookupIP not implemented\")\n}\nfunc (UnimplementedDaemonServer) ResolvePort(context.Context, *ResolvePortRequest) (*ResolvePortResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ResolvePort not implemented\")\n}\nfunc (UnimplementedDaemonServer) RerouteRemotePort(context.Context, *ReroutePortRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RerouteRemotePort not implemented\")\n}\nfunc (UnimplementedDaemonServer) ActivityWatcher(*emptypb.Empty, grpc.ServerStreamingServer[Activity]) error {\n\treturn status.Error(codes.Unimplemented, \"method ActivityWatcher not implemented\")\n}\nfunc (UnimplementedDaemonServer) mustEmbedUnimplementedDaemonServer() {}\nfunc (UnimplementedDaemonServer) testEmbeddedByValue()                {}\n\n// UnsafeDaemonServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to DaemonServer will\n// result in compilation errors.\ntype UnsafeDaemonServer interface {\n\tmustEmbedUnimplementedDaemonServer()\n}\n\nfunc RegisterDaemonServer(s grpc.ServiceRegistrar, srv DaemonServer) {\n\t// If the following call panics, it indicates UnimplementedDaemonServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&Daemon_ServiceDesc, srv)\n}\n\nfunc _Daemon_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).Version(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_Version_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).Version(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).Status(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_Status_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).Status(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_Quit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).Quit(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_Quit_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).Quit(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_Connect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(NetworkConfig)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).Connect(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_Connect_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).Connect(ctx, req.(*NetworkConfig))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_Disconnect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).Disconnect(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_Disconnect_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).Disconnect(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_GetNetworkConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).GetNetworkConfig(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_GetNetworkConfig_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).GetNetworkConfig(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_SetDNSTopLevelDomains_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Domains)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).SetDNSTopLevelDomains(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_SetDNSTopLevelDomains_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).SetDNSTopLevelDomains(ctx, req.(*Domains))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_SetDNSExcludes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SetDNSExcludesRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).SetDNSExcludes(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_SetDNSExcludes_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).SetDNSExcludes(ctx, req.(*SetDNSExcludesRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_SetDNSMappings_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SetDNSMappingsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).SetDNSMappings(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_SetDNSMappings_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).SetDNSMappings(ctx, req.(*SetDNSMappingsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_SetLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(manager.LogLevelRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).SetLogLevel(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_SetLogLevel_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).SetLogLevel(ctx, req.(*manager.LogLevelRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_TranslateEnvIPs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(Environment)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).TranslateEnvIPs(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_TranslateEnvIPs_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).TranslateEnvIPs(ctx, req.(*Environment))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_WaitForNetwork_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).WaitForNetwork(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_WaitForNetwork_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).WaitForNetwork(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_WaitForAgentIP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(WaitForAgentIPRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).WaitForAgentIP(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_WaitForAgentIP_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).WaitForAgentIP(ctx, req.(*WaitForAgentIPRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_LookupIP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LookupIPRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).LookupIP(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_LookupIP_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).LookupIP(ctx, req.(*LookupIPRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_ResolvePort_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ResolvePortRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).ResolvePort(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_ResolvePort_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).ResolvePort(ctx, req.(*ResolvePortRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_RerouteRemotePort_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ReroutePortRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DaemonServer).RerouteRemotePort(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Daemon_RerouteRemotePort_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DaemonServer).RerouteRemotePort(ctx, req.(*ReroutePortRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Daemon_ActivityWatcher_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(emptypb.Empty)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(DaemonServer).ActivityWatcher(m, &grpc.GenericServerStream[emptypb.Empty, Activity]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Daemon_ActivityWatcherServer = grpc.ServerStreamingServer[Activity]\n\n// Daemon_ServiceDesc is the grpc.ServiceDesc for Daemon service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar Daemon_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"telepresence.daemon.Daemon\",\n\tHandlerType: (*DaemonServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Version\",\n\t\t\tHandler:    _Daemon_Version_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Status\",\n\t\t\tHandler:    _Daemon_Status_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Quit\",\n\t\t\tHandler:    _Daemon_Quit_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Connect\",\n\t\t\tHandler:    _Daemon_Connect_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Disconnect\",\n\t\t\tHandler:    _Daemon_Disconnect_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetNetworkConfig\",\n\t\t\tHandler:    _Daemon_GetNetworkConfig_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetDNSTopLevelDomains\",\n\t\t\tHandler:    _Daemon_SetDNSTopLevelDomains_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetDNSExcludes\",\n\t\t\tHandler:    _Daemon_SetDNSExcludes_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetDNSMappings\",\n\t\t\tHandler:    _Daemon_SetDNSMappings_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetLogLevel\",\n\t\t\tHandler:    _Daemon_SetLogLevel_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"TranslateEnvIPs\",\n\t\t\tHandler:    _Daemon_TranslateEnvIPs_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"WaitForNetwork\",\n\t\t\tHandler:    _Daemon_WaitForNetwork_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"WaitForAgentIP\",\n\t\t\tHandler:    _Daemon_WaitForAgentIP_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"LookupIP\",\n\t\t\tHandler:    _Daemon_LookupIP_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ResolvePort\",\n\t\t\tHandler:    _Daemon_ResolvePort_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RerouteRemotePort\",\n\t\t\tHandler:    _Daemon_RerouteRemotePort_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"ActivityWatcher\",\n\t\t\tHandler:       _Daemon_ActivityWatcher_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"daemon/daemon.proto\",\n}\n"
  },
  {
    "path": "rpc/go.mod",
    "content": "module github.com/telepresenceio/telepresence/rpc/v2\n\ngo 1.25.0\n\nrequire (\n\tgoogle.golang.org/grpc v1.79.1\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tgolang.org/x/net v0.51.0 // indirect\n\tgolang.org/x/sys v0.41.0 // indirect\n\tgolang.org/x/text v0.34.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect\n)\n"
  },
  {
    "path": "rpc/go.sum",
    "content": "github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\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=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\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/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=\ngolang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=\ngolang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=\ngoogle.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\n"
  },
  {
    "path": "rpc/manager/manager.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.30.2\n// source: manager/manager.proto\n\n// The \"manager\" package describes the server implemented by the\n// in-cluster Manager, which is spoken to by the Agent (app-sidecar),\n// the on-laptop Connector (user-daemon), and the on-laptop CLI.\n\npackage manager\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\tdurationpb \"google.golang.org/protobuf/types/known/durationpb\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n\ttimestamppb \"google.golang.org/protobuf/types/known/timestamppb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype InterceptDispositionType int32\n\nconst (\n\tInterceptDispositionType_UNSPECIFIED InterceptDispositionType = 0\n\tInterceptDispositionType_ACTIVE      InterceptDispositionType = 1\n\tInterceptDispositionType_WAITING     InterceptDispositionType = 2\n\tInterceptDispositionType_REMOVED     InterceptDispositionType = 9\n\t// What does \"NO_CLIENT\" mean?  The Manager garbage-collects the\n\t// intercept if the client goes away.\n\tInterceptDispositionType_NO_CLIENT InterceptDispositionType = 3\n\t// NO_AGENT indicates that there are no currently-running agents\n\t// that can service the intercept, or that there is a inconsistency\n\t// between the agents that are running.  This may be an ephemeral\n\t// state, such as inconsistency between agents during the middle of\n\t// a rolling update.\n\tInterceptDispositionType_NO_AGENT InterceptDispositionType = 4\n\t// NO_MECHANISM indicates that the agent(s) that would handle this\n\t// intercept do not report that they support the mechanism of the\n\t// intercept.  For example, if you are running the OSS agent but ask\n\t// for an intercept using the \"http\" mechanism, which requires the\n\t// Ambassador Telepresence agent.\n\tInterceptDispositionType_NO_MECHANISM InterceptDispositionType = 5\n\t// NO_PORT indicates that the manager was unable to allocate a port\n\t// to act as the rendezvous point between the client and the agent.\n\tInterceptDispositionType_NO_PORTS InterceptDispositionType = 6\n\t// AGENT_ERROR indicates that the intercept was submitted to an\n\t// agent, but that the agent rejected it (by calling\n\t// ReviewIntercept).\n\tInterceptDispositionType_AGENT_ERROR InterceptDispositionType = 7\n\t// BAD_ARGS indicates that something about the mechanism_args is\n\t// invalid.\n\tInterceptDispositionType_BAD_ARGS InterceptDispositionType = 8\n)\n\n// Enum value maps for InterceptDispositionType.\nvar (\n\tInterceptDispositionType_name = map[int32]string{\n\t\t0: \"UNSPECIFIED\",\n\t\t1: \"ACTIVE\",\n\t\t2: \"WAITING\",\n\t\t9: \"REMOVED\",\n\t\t3: \"NO_CLIENT\",\n\t\t4: \"NO_AGENT\",\n\t\t5: \"NO_MECHANISM\",\n\t\t6: \"NO_PORTS\",\n\t\t7: \"AGENT_ERROR\",\n\t\t8: \"BAD_ARGS\",\n\t}\n\tInterceptDispositionType_value = map[string]int32{\n\t\t\"UNSPECIFIED\":  0,\n\t\t\"ACTIVE\":       1,\n\t\t\"WAITING\":      2,\n\t\t\"REMOVED\":      9,\n\t\t\"NO_CLIENT\":    3,\n\t\t\"NO_AGENT\":     4,\n\t\t\"NO_MECHANISM\": 5,\n\t\t\"NO_PORTS\":     6,\n\t\t\"AGENT_ERROR\":  7,\n\t\t\"BAD_ARGS\":     8,\n\t}\n)\n\nfunc (x InterceptDispositionType) Enum() *InterceptDispositionType {\n\tp := new(InterceptDispositionType)\n\t*p = x\n\treturn p\n}\n\nfunc (x InterceptDispositionType) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (InterceptDispositionType) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_manager_manager_proto_enumTypes[0].Descriptor()\n}\n\nfunc (InterceptDispositionType) Type() protoreflect.EnumType {\n\treturn &file_manager_manager_proto_enumTypes[0]\n}\n\nfunc (x InterceptDispositionType) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use InterceptDispositionType.Descriptor instead.\nfunc (InterceptDispositionType) EnumDescriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{0}\n}\n\ntype WorkloadInfo_Kind int32\n\nconst (\n\tWorkloadInfo_UNSPECIFIED WorkloadInfo_Kind = 0\n\tWorkloadInfo_DEPLOYMENT  WorkloadInfo_Kind = 1\n\tWorkloadInfo_REPLICASET  WorkloadInfo_Kind = 2\n\tWorkloadInfo_STATEFULSET WorkloadInfo_Kind = 3\n\tWorkloadInfo_ROLLOUT     WorkloadInfo_Kind = 4\n)\n\n// Enum value maps for WorkloadInfo_Kind.\nvar (\n\tWorkloadInfo_Kind_name = map[int32]string{\n\t\t0: \"UNSPECIFIED\",\n\t\t1: \"DEPLOYMENT\",\n\t\t2: \"REPLICASET\",\n\t\t3: \"STATEFULSET\",\n\t\t4: \"ROLLOUT\",\n\t}\n\tWorkloadInfo_Kind_value = map[string]int32{\n\t\t\"UNSPECIFIED\": 0,\n\t\t\"DEPLOYMENT\":  1,\n\t\t\"REPLICASET\":  2,\n\t\t\"STATEFULSET\": 3,\n\t\t\"ROLLOUT\":     4,\n\t}\n)\n\nfunc (x WorkloadInfo_Kind) Enum() *WorkloadInfo_Kind {\n\tp := new(WorkloadInfo_Kind)\n\t*p = x\n\treturn p\n}\n\nfunc (x WorkloadInfo_Kind) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (WorkloadInfo_Kind) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_manager_manager_proto_enumTypes[1].Descriptor()\n}\n\nfunc (WorkloadInfo_Kind) Type() protoreflect.EnumType {\n\treturn &file_manager_manager_proto_enumTypes[1]\n}\n\nfunc (x WorkloadInfo_Kind) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use WorkloadInfo_Kind.Descriptor instead.\nfunc (WorkloadInfo_Kind) EnumDescriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{45, 0}\n}\n\ntype WorkloadInfo_State int32\n\nconst (\n\t// The state of this workload is not known.\n\tWorkloadInfo_UNKNOWN_UNSPECIFIED WorkloadInfo_State = 0\n\t// Available means the deployment is available, ie. at least the minimum available\n\t// replicas required are up and running for at least minReadySeconds.\n\tWorkloadInfo_AVAILABLE WorkloadInfo_State = 1\n\t// Progressing means the workload is progressing. Progress for a workload is\n\t// considered when a new replica set is created or adopted, and when new pods scale\n\t// up or old pods scale down. Progress is not estimated for paused workloads or\n\t// when progressDeadlineSeconds is not specified.\n\tWorkloadInfo_PROGRESSING WorkloadInfo_State = 2\n\t// FAILURE means that one of its pods fails to be created or deleted.\n\tWorkloadInfo_FAILURE WorkloadInfo_State = 3\n)\n\n// Enum value maps for WorkloadInfo_State.\nvar (\n\tWorkloadInfo_State_name = map[int32]string{\n\t\t0: \"UNKNOWN_UNSPECIFIED\",\n\t\t1: \"AVAILABLE\",\n\t\t2: \"PROGRESSING\",\n\t\t3: \"FAILURE\",\n\t}\n\tWorkloadInfo_State_value = map[string]int32{\n\t\t\"UNKNOWN_UNSPECIFIED\": 0,\n\t\t\"AVAILABLE\":           1,\n\t\t\"PROGRESSING\":         2,\n\t\t\"FAILURE\":             3,\n\t}\n)\n\nfunc (x WorkloadInfo_State) Enum() *WorkloadInfo_State {\n\tp := new(WorkloadInfo_State)\n\t*p = x\n\treturn p\n}\n\nfunc (x WorkloadInfo_State) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (WorkloadInfo_State) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_manager_manager_proto_enumTypes[2].Descriptor()\n}\n\nfunc (WorkloadInfo_State) Type() protoreflect.EnumType {\n\treturn &file_manager_manager_proto_enumTypes[2]\n}\n\nfunc (x WorkloadInfo_State) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use WorkloadInfo_State.Descriptor instead.\nfunc (WorkloadInfo_State) EnumDescriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{45, 1}\n}\n\ntype WorkloadInfo_AgentState int32\n\nconst (\n\t// Workload has never been intercepted, so no agent has been installed.\n\tWorkloadInfo_NO_AGENT_UNSPECIFIED WorkloadInfo_AgentState = 0\n\t// An agent has been installed into workload's pods, but it is not currently intercepted.\n\tWorkloadInfo_INSTALLED WorkloadInfo_AgentState = 1\n\t// The workload (or rather its pods) is currently intercepted.\n\tWorkloadInfo_INTERCEPTED WorkloadInfo_AgentState = 2\n)\n\n// Enum value maps for WorkloadInfo_AgentState.\nvar (\n\tWorkloadInfo_AgentState_name = map[int32]string{\n\t\t0: \"NO_AGENT_UNSPECIFIED\",\n\t\t1: \"INSTALLED\",\n\t\t2: \"INTERCEPTED\",\n\t}\n\tWorkloadInfo_AgentState_value = map[string]int32{\n\t\t\"NO_AGENT_UNSPECIFIED\": 0,\n\t\t\"INSTALLED\":            1,\n\t\t\"INTERCEPTED\":          2,\n\t}\n)\n\nfunc (x WorkloadInfo_AgentState) Enum() *WorkloadInfo_AgentState {\n\tp := new(WorkloadInfo_AgentState)\n\t*p = x\n\treturn p\n}\n\nfunc (x WorkloadInfo_AgentState) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (WorkloadInfo_AgentState) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_manager_manager_proto_enumTypes[3].Descriptor()\n}\n\nfunc (WorkloadInfo_AgentState) Type() protoreflect.EnumType {\n\treturn &file_manager_manager_proto_enumTypes[3]\n}\n\nfunc (x WorkloadInfo_AgentState) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use WorkloadInfo_AgentState.Descriptor instead.\nfunc (WorkloadInfo_AgentState) EnumDescriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{45, 2}\n}\n\ntype WorkloadEvent_Type int32\n\nconst (\n\tWorkloadEvent_ADDED_UNSPECIFIED WorkloadEvent_Type = 0\n\tWorkloadEvent_MODIFIED          WorkloadEvent_Type = 1\n\tWorkloadEvent_DELETED           WorkloadEvent_Type = 2\n)\n\n// Enum value maps for WorkloadEvent_Type.\nvar (\n\tWorkloadEvent_Type_name = map[int32]string{\n\t\t0: \"ADDED_UNSPECIFIED\",\n\t\t1: \"MODIFIED\",\n\t\t2: \"DELETED\",\n\t}\n\tWorkloadEvent_Type_value = map[string]int32{\n\t\t\"ADDED_UNSPECIFIED\": 0,\n\t\t\"MODIFIED\":          1,\n\t\t\"DELETED\":           2,\n\t}\n)\n\nfunc (x WorkloadEvent_Type) Enum() *WorkloadEvent_Type {\n\tp := new(WorkloadEvent_Type)\n\t*p = x\n\treturn p\n}\n\nfunc (x WorkloadEvent_Type) String() string {\n\treturn protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))\n}\n\nfunc (WorkloadEvent_Type) Descriptor() protoreflect.EnumDescriptor {\n\treturn file_manager_manager_proto_enumTypes[4].Descriptor()\n}\n\nfunc (WorkloadEvent_Type) Type() protoreflect.EnumType {\n\treturn &file_manager_manager_proto_enumTypes[4]\n}\n\nfunc (x WorkloadEvent_Type) Number() protoreflect.EnumNumber {\n\treturn protoreflect.EnumNumber(x)\n}\n\n// Deprecated: Use WorkloadEvent_Type.Descriptor instead.\nfunc (WorkloadEvent_Type) EnumDescriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{46, 0}\n}\n\n// ClientInfo is the self-reported metadata that the on-laptop\n// Telepresence client reports whenever it connects to the in-cluster\n// Manager.\ntype ClientInfo struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`           // user@hostname\n\tNamespace     string                 `protobuf:\"bytes,6,opt,name=namespace,proto3\" json:\"namespace,omitempty\"` // namespace that the client is connected to\n\tInstallId     string                 `protobuf:\"bytes,2,opt,name=install_id,json=installId,proto3\" json:\"install_id,omitempty\"`\n\tProduct       string                 `protobuf:\"bytes,3,opt,name=product,proto3\" json:\"product,omitempty\"` // \"telepresence\"\n\tVersion       string                 `protobuf:\"bytes,4,opt,name=version,proto3\" json:\"version,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClientInfo) Reset() {\n\t*x = ClientInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientInfo) ProtoMessage() {}\n\nfunc (x *ClientInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientInfo.ProtoReflect.Descriptor instead.\nfunc (*ClientInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ClientInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientInfo) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientInfo) GetInstallId() string {\n\tif x != nil {\n\t\treturn x.InstallId\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientInfo) GetProduct() string {\n\tif x != nil {\n\t\treturn x.Product\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientInfo) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\n// AgentInfo is the self-reported metadata that an Agent (app-sidecar)\n// reports at boot-up when it connects to the Telepresence Manager.\ntype AgentInfo struct {\n\tstate     protoimpl.MessageState `protogen:\"open.v1\"`\n\tName      string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"` // name of the Workload\n\tKind      string                 `protobuf:\"bytes,13,opt,name=kind,proto3\" json:\"kind,omitempty\"`\n\tNamespace string                 `protobuf:\"bytes,7,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`                 // namespace of the Workload\n\tPodName   string                 `protobuf:\"bytes,8,opt,name=pod_name,json=podName,proto3\" json:\"pod_name,omitempty\"`      // Pod name (from metadata.name)\n\tPodIp     string                 `protobuf:\"bytes,2,opt,name=pod_ip,json=podIp,proto3\" json:\"pod_ip,omitempty\"`            // Pod IP (from status.podIP)\n\tPodUid    string                 `protobuf:\"bytes,14,opt,name=pod_uid,json=podUid,proto3\" json:\"pod_uid,omitempty\"`        // Pod UID\n\tApiPort   int32                  `protobuf:\"varint,9,opt,name=api_port,json=apiPort,proto3\" json:\"api_port,omitempty\"`     // Port number for the agent gRPC API\n\tSftpPort  int32                  `protobuf:\"varint,10,opt,name=sftp_port,json=sftpPort,proto3\" json:\"sftp_port,omitempty\"` // Port number for the agent SFTP server\n\tFtpPort   int32                  `protobuf:\"varint,11,opt,name=ftp_port,json=ftpPort,proto3\" json:\"ftp_port,omitempty\"`    // Port number for the agent FTP server\n\tProduct   string                 `protobuf:\"bytes,3,opt,name=product,proto3\" json:\"product,omitempty\"`                     // distinguish open source, our closed source, someone else's thing\n\tVersion   string                 `protobuf:\"bytes,4,opt,name=version,proto3\" json:\"version,omitempty\"`\n\t// This is a list of the mechanisms that the Agent advertises that\n\t// it supports.\n\tMechanisms    []*AgentInfo_Mechanism              `protobuf:\"bytes,5,rep,name=mechanisms,proto3\" json:\"mechanisms,omitempty\"`\n\tContainers    map[string]*AgentInfo_ContainerInfo `protobuf:\"bytes,12,rep,name=containers,proto3\" json:\"containers,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentInfo) Reset() {\n\t*x = AgentInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentInfo) ProtoMessage() {}\n\nfunc (x *AgentInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentInfo.ProtoReflect.Descriptor instead.\nfunc (*AgentInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *AgentInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo) GetKind() string {\n\tif x != nil {\n\t\treturn x.Kind\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo) GetPodName() string {\n\tif x != nil {\n\t\treturn x.PodName\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo) GetPodIp() string {\n\tif x != nil {\n\t\treturn x.PodIp\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo) GetPodUid() string {\n\tif x != nil {\n\t\treturn x.PodUid\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo) GetApiPort() int32 {\n\tif x != nil {\n\t\treturn x.ApiPort\n\t}\n\treturn 0\n}\n\nfunc (x *AgentInfo) GetSftpPort() int32 {\n\tif x != nil {\n\t\treturn x.SftpPort\n\t}\n\treturn 0\n}\n\nfunc (x *AgentInfo) GetFtpPort() int32 {\n\tif x != nil {\n\t\treturn x.FtpPort\n\t}\n\treturn 0\n}\n\nfunc (x *AgentInfo) GetProduct() string {\n\tif x != nil {\n\t\treturn x.Product\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo) GetMechanisms() []*AgentInfo_Mechanism {\n\tif x != nil {\n\t\treturn x.Mechanisms\n\t}\n\treturn nil\n}\n\nfunc (x *AgentInfo) GetContainers() map[string]*AgentInfo_ContainerInfo {\n\tif x != nil {\n\t\treturn x.Containers\n\t}\n\treturn nil\n}\n\n// PortMapping describes a mapping from a port number in the intercepted container to\n// a port on the client for --from-pod and or vice versa when using --to-pod.\ntype PortMapping struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tFrom          int32                  `protobuf:\"varint,1,opt,name=from,proto3\" json:\"from,omitempty\"`\n\tTo            int32                  `protobuf:\"varint,2,opt,name=to,proto3\" json:\"to,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *PortMapping) Reset() {\n\t*x = PortMapping{}\n\tmi := &file_manager_manager_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PortMapping) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PortMapping) ProtoMessage() {}\n\nfunc (x *PortMapping) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PortMapping.ProtoReflect.Descriptor instead.\nfunc (*PortMapping) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *PortMapping) GetFrom() int32 {\n\tif x != nil {\n\t\treturn x.From\n\t}\n\treturn 0\n}\n\nfunc (x *PortMapping) GetTo() int32 {\n\tif x != nil {\n\t\treturn x.To\n\t}\n\treturn 0\n}\n\n// InterceptSpec contains static information about an intercept. It is shared by\n// all running agent instances.\ntype InterceptSpec struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// A human-friendly name for this intercept.  This is usually the\n\t// same as the agent name below; the name/namespace of the\n\t// Workload, but it could be something else.  It is invalid for\n\t// the same client to attempt to create multiple intercepts with the\n\t// same name.\n\tName string `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\t// Same as ClientInfo.Name; \"user@hostname\".\n\tClient string `protobuf:\"bytes,2,opt,name=client,proto3\" json:\"client,omitempty\"`\n\t// Same as AgentInfo.Name of the Workload.\n\tAgent string `protobuf:\"bytes,3,opt,name=agent,proto3\" json:\"agent,omitempty\"`\n\t// Kind of the Workload\n\tWorkloadKind string `protobuf:\"bytes,13,opt,name=workload_kind,json=workloadKind,proto3\" json:\"workload_kind,omitempty\"`\n\t// Same as AgentInfo.Namespace of the Workload\n\tNamespace string `protobuf:\"bytes,8,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\t// How to decide which subset of requests to that agent to intercept.\n\tMechanism string `protobuf:\"bytes,4,opt,name=mechanism,proto3\" json:\"mechanism,omitempty\"`\n\t// The host that the target_ports are routed to.\n\tTargetHost string `protobuf:\"bytes,6,opt,name=target_host,json=targetHost,proto3\" json:\"target_host,omitempty\"`\n\t// Ports that will be forwarded from the intercepting pod's IP address\n\t// to the target_host, using the following syntax:\n\t//\n\t// PORT = port-decl [\"/\" protocol ]\n\t// port-decl = port-spec [ \":\" uint16 ]\n\t// protocol = \"TCP\" | \"UDP\"\n\t// port-spec = name | uint16\n\t//\n\t// If two numbers are used, they signify source:destination.\n\tPodPorts []string `protobuf:\"bytes,5,rep,name=pod_ports,json=podPorts,proto3\" json:\"pod_ports,omitempty\"`\n\t// Ports that will be forwarded from the intercepting client's localhost\n\t// to the intercepted pod. Uses the same syntax as target_ports.\n\tLocalPorts []string `protobuf:\"bytes,18,rep,name=local_ports,json=localPorts,proto3\" json:\"local_ports,omitempty\"`\n\t// Identifier for the service or container port: either the name or port number\n\t// optionally followed by a \"/TCP\" or \"/UDP\"\n\tPortIdentifier string `protobuf:\"bytes,10,opt,name=port_identifier,json=portIdentifier,proto3\" json:\"port_identifier,omitempty\"`\n\t// The resolved service port name\n\tServicePortName string `protobuf:\"bytes,19,opt,name=service_port_name,json=servicePortName,proto3\" json:\"service_port_name,omitempty\"`\n\t// The resolved service port\n\tServicePort int32 `protobuf:\"varint,20,opt,name=service_port,json=servicePort,proto3\" json:\"service_port,omitempty\"`\n\t// .uid.metadata of service associated with intercept\n\tServiceUid string `protobuf:\"bytes,12,opt,name=service_uid,json=serviceUid,proto3\" json:\"service_uid,omitempty\"`\n\t// name of the aforementioned service\n\tServiceName string `protobuf:\"bytes,14,opt,name=service_name,json=serviceName,proto3\" json:\"service_name,omitempty\"`\n\t// The resolved protocol used by the container port\n\tProtocol string `protobuf:\"bytes,21,opt,name=protocol,proto3\" json:\"protocol,omitempty\"` // TCP or UDP\n\t// Name of container that provides environment and mounts. This is normally the container\n\t// that owns the container_port, but in some cases it will differ because the container_port\n\t// is owned by some kind of routing mechanism (such as nginx).\n\tContainerName string `protobuf:\"bytes,24,opt,name=container_name,json=containerName,proto3\" json:\"container_name,omitempty\"`\n\t// The resolved container port that is intercepted.\n\tContainerPort int32 `protobuf:\"varint,23,opt,name=container_port,json=containerPort,proto3\" json:\"container_port,omitempty\"`\n\t// The port on the workstation that the intercepted container_port is redirected to.\n\tTargetPort int32 `protobuf:\"varint,7,opt,name=target_port,json=targetPort,proto3\" json:\"target_port,omitempty\"`\n\t// The delay imposed by a call roundtrip between the traffic-agent and\n\t// the client on the workstation. This delay is added to the dial_timeout\n\t// when the workstation performs a dial on behalf of the traffic-agent.\n\tRoundtripLatency int64 `protobuf:\"varint,16,opt,name=roundtrip_latency,json=roundtripLatency,proto3\" json:\"roundtrip_latency,omitempty\"`\n\t// The dial timeout to use when a dial is made on the intercepting workstation.\n\tDialTimeout int64 `protobuf:\"varint,17,opt,name=dial_timeout,json=dialTimeout,proto3\" json:\"dial_timeout,omitempty\"`\n\t// Extra ports that will be forwarded from the intercepting client's localhost\n\t// to the intercepted pod.\n\t// Deprecated: use local_ports instead\n\tExtraPorts []int32 `protobuf:\"varint,15,rep,packed,name=extra_ports,json=extraPorts,proto3\" json:\"extra_ports,omitempty\"`\n\t// Whether to replace the running container.\n\tReplace bool `protobuf:\"varint,22,opt,name=replace,proto3\" json:\"replace,omitempty\"`\n\t// place a wiretap on intercepted ports instead of redirecting them\n\tWiretap bool `protobuf:\"varint,26,opt,name=wiretap,proto3\" json:\"wiretap,omitempty\"`\n\t// Intercept desire no default port.\n\tNoDefaultPort bool `protobuf:\"varint,25,opt,name=no_default_port,json=noDefaultPort,proto3\" json:\"no_default_port,omitempty\"`\n\t// HTTP header filters for HTTP Intercepts. Only requests containing\n\t// these headers (key=value) will be intercepted. Multiple headers use AND logic.\n\tHeaderFilters map[string]string `protobuf:\"bytes,27,rep,name=header_filters,json=headerFilters,proto3\" json:\"header_filters,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// Path filter patterns for HTTP Intercepts. Supports glob patterns like \"/api/v1/*\".\n\t// If specified, only requests matching these paths will be intercepted.\n\tPathFilters []string `protobuf:\"bytes,28,rep,name=path_filters,json=pathFilters,proto3\" json:\"path_filters,omitempty\"`\n\t// Metadata to associate with the intercept. Retrievable using the API server.\n\tMetadata map[string]string `protobuf:\"bytes,29,rep,name=metadata,proto3\" json:\"metadata,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// Plaintext instructs the traffic-agent to use plain text when communicating with the client.\n\tPlaintext     bool `protobuf:\"varint,30,opt,name=plaintext,proto3\" json:\"plaintext,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *InterceptSpec) Reset() {\n\t*x = InterceptSpec{}\n\tmi := &file_manager_manager_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *InterceptSpec) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InterceptSpec) ProtoMessage() {}\n\nfunc (x *InterceptSpec) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InterceptSpec.ProtoReflect.Descriptor instead.\nfunc (*InterceptSpec) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *InterceptSpec) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetClient() string {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetAgent() string {\n\tif x != nil {\n\t\treturn x.Agent\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetWorkloadKind() string {\n\tif x != nil {\n\t\treturn x.WorkloadKind\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetMechanism() string {\n\tif x != nil {\n\t\treturn x.Mechanism\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetTargetHost() string {\n\tif x != nil {\n\t\treturn x.TargetHost\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetPodPorts() []string {\n\tif x != nil {\n\t\treturn x.PodPorts\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptSpec) GetLocalPorts() []string {\n\tif x != nil {\n\t\treturn x.LocalPorts\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptSpec) GetPortIdentifier() string {\n\tif x != nil {\n\t\treturn x.PortIdentifier\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetServicePortName() string {\n\tif x != nil {\n\t\treturn x.ServicePortName\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetServicePort() int32 {\n\tif x != nil {\n\t\treturn x.ServicePort\n\t}\n\treturn 0\n}\n\nfunc (x *InterceptSpec) GetServiceUid() string {\n\tif x != nil {\n\t\treturn x.ServiceUid\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetServiceName() string {\n\tif x != nil {\n\t\treturn x.ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetProtocol() string {\n\tif x != nil {\n\t\treturn x.Protocol\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetContainerName() string {\n\tif x != nil {\n\t\treturn x.ContainerName\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptSpec) GetContainerPort() int32 {\n\tif x != nil {\n\t\treturn x.ContainerPort\n\t}\n\treturn 0\n}\n\nfunc (x *InterceptSpec) GetTargetPort() int32 {\n\tif x != nil {\n\t\treturn x.TargetPort\n\t}\n\treturn 0\n}\n\nfunc (x *InterceptSpec) GetRoundtripLatency() int64 {\n\tif x != nil {\n\t\treturn x.RoundtripLatency\n\t}\n\treturn 0\n}\n\nfunc (x *InterceptSpec) GetDialTimeout() int64 {\n\tif x != nil {\n\t\treturn x.DialTimeout\n\t}\n\treturn 0\n}\n\nfunc (x *InterceptSpec) GetExtraPorts() []int32 {\n\tif x != nil {\n\t\treturn x.ExtraPorts\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptSpec) GetReplace() bool {\n\tif x != nil {\n\t\treturn x.Replace\n\t}\n\treturn false\n}\n\nfunc (x *InterceptSpec) GetWiretap() bool {\n\tif x != nil {\n\t\treturn x.Wiretap\n\t}\n\treturn false\n}\n\nfunc (x *InterceptSpec) GetNoDefaultPort() bool {\n\tif x != nil {\n\t\treturn x.NoDefaultPort\n\t}\n\treturn false\n}\n\nfunc (x *InterceptSpec) GetHeaderFilters() map[string]string {\n\tif x != nil {\n\t\treturn x.HeaderFilters\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptSpec) GetPathFilters() []string {\n\tif x != nil {\n\t\treturn x.PathFilters\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptSpec) GetMetadata() map[string]string {\n\tif x != nil {\n\t\treturn x.Metadata\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptSpec) GetPlaintext() bool {\n\tif x != nil {\n\t\treturn x.Plaintext\n\t}\n\treturn false\n}\n\n// InterceptInfo contains information about a live intercept in an agent\ntype InterceptInfo struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSpec          *InterceptSpec         `protobuf:\"bytes,1,opt,name=spec,proto3\" json:\"spec,omitempty\"`\n\tId            string                 `protobuf:\"bytes,5,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tClientSession *SessionInfo           `protobuf:\"bytes,6,opt,name=client_session,json=clientSession,proto3\" json:\"client_session,omitempty\"`\n\t// The current intercept state; a status code and a human-friendly\n\t// message to go along with the status code.  These may be set\n\t// manager itself, or may be set by the agent's call to\n\t// ReviewIntercept.\n\tDisposition InterceptDispositionType `protobuf:\"varint,3,opt,name=disposition,proto3,enum=telepresence.manager.InterceptDispositionType\" json:\"disposition,omitempty\"`\n\tMessage     string                   `protobuf:\"bytes,4,opt,name=message,proto3\" json:\"message,omitempty\"`\n\t// Name and port to use when establishing port-forward to the pod's\n\t// gRPC API.\n\tPodName  string `protobuf:\"bytes,19,opt,name=pod_name,json=podName,proto3\" json:\"pod_name,omitempty\"`\n\tApiPort  int32  `protobuf:\"varint,20,opt,name=api_port,json=apiPort,proto3\" json:\"api_port,omitempty\"`\n\tPodIp    string `protobuf:\"bytes,10,opt,name=pod_ip,json=podIp,proto3\" json:\"pod_ip,omitempty\"`\n\tSftpPort int32  `protobuf:\"varint,11,opt,name=sftp_port,json=sftpPort,proto3\" json:\"sftp_port,omitempty\"`\n\tFtpPort  int32  `protobuf:\"varint,18,opt,name=ftp_port,json=ftpPort,proto3\" json:\"ftp_port,omitempty\"`\n\t// The directory where the client mounts the remote mount_point. Only\n\t// set when obtaining InterceptInfo from the user daemon.\n\tClientMountPoint string `protobuf:\"bytes,2,opt,name=client_mount_point,json=clientMountPoint,proto3\" json:\"client_mount_point,omitempty\"`\n\t// The directory where the intercept mounts can be found in the agent\n\tMountPoint string `protobuf:\"bytes,16,opt,name=mount_point,json=mountPoint,proto3\" json:\"mount_point,omitempty\"`\n\t// A human-friendly description of what the spec.mechanism_args say.\n\t// This is set by the agent's call to ReviewIntercept.\n\tMechanismArgsDesc string `protobuf:\"bytes,12,opt,name=mechanism_args_desc,json=mechanismArgsDesc,proto3\" json:\"mechanism_args_desc,omitempty\"`\n\t// The environment of the intercepted app\n\tEnvironment map[string]string `protobuf:\"bytes,17,rep,name=environment,proto3\" json:\"environment,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// Map of mount path -> MountPolicy\n\tMounts map[string]int32 `protobuf:\"bytes,22,rep,name=mounts,proto3\" json:\"mounts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"varint,2,opt,name=value\"`\n\t// Timestamp for last modification made by traffic-manager\n\tModifiedAt    *timestamppb.Timestamp `protobuf:\"bytes,21,opt,name=modified_at,json=modifiedAt,proto3\" json:\"modified_at,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *InterceptInfo) Reset() {\n\t*x = InterceptInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *InterceptInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InterceptInfo) ProtoMessage() {}\n\nfunc (x *InterceptInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InterceptInfo.ProtoReflect.Descriptor instead.\nfunc (*InterceptInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *InterceptInfo) GetSpec() *InterceptSpec {\n\tif x != nil {\n\t\treturn x.Spec\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptInfo) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptInfo) GetClientSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.ClientSession\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptInfo) GetDisposition() InterceptDispositionType {\n\tif x != nil {\n\t\treturn x.Disposition\n\t}\n\treturn InterceptDispositionType_UNSPECIFIED\n}\n\nfunc (x *InterceptInfo) GetMessage() string {\n\tif x != nil {\n\t\treturn x.Message\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptInfo) GetPodName() string {\n\tif x != nil {\n\t\treturn x.PodName\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptInfo) GetApiPort() int32 {\n\tif x != nil {\n\t\treturn x.ApiPort\n\t}\n\treturn 0\n}\n\nfunc (x *InterceptInfo) GetPodIp() string {\n\tif x != nil {\n\t\treturn x.PodIp\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptInfo) GetSftpPort() int32 {\n\tif x != nil {\n\t\treturn x.SftpPort\n\t}\n\treturn 0\n}\n\nfunc (x *InterceptInfo) GetFtpPort() int32 {\n\tif x != nil {\n\t\treturn x.FtpPort\n\t}\n\treturn 0\n}\n\nfunc (x *InterceptInfo) GetClientMountPoint() string {\n\tif x != nil {\n\t\treturn x.ClientMountPoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptInfo) GetMountPoint() string {\n\tif x != nil {\n\t\treturn x.MountPoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptInfo) GetMechanismArgsDesc() string {\n\tif x != nil {\n\t\treturn x.MechanismArgsDesc\n\t}\n\treturn \"\"\n}\n\nfunc (x *InterceptInfo) GetEnvironment() map[string]string {\n\tif x != nil {\n\t\treturn x.Environment\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptInfo) GetMounts() map[string]int32 {\n\tif x != nil {\n\t\treturn x.Mounts\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptInfo) GetModifiedAt() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.ModifiedAt\n\t}\n\treturn nil\n}\n\ntype ReconnectAgentRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tAgent         *AgentInfo             `protobuf:\"bytes,2,opt,name=agent,proto3\" json:\"agent,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ReconnectAgentRequest) Reset() {\n\t*x = ReconnectAgentRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReconnectAgentRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReconnectAgentRequest) ProtoMessage() {}\n\nfunc (x *ReconnectAgentRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReconnectAgentRequest.ProtoReflect.Descriptor instead.\nfunc (*ReconnectAgentRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *ReconnectAgentRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *ReconnectAgentRequest) GetAgent() *AgentInfo {\n\tif x != nil {\n\t\treturn x.Agent\n\t}\n\treturn nil\n}\n\ntype ReconnectClientRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tClient        *ClientInfo            `protobuf:\"bytes,2,opt,name=client,proto3\" json:\"client,omitempty\"`\n\tIntercepts    []*InterceptInfo       `protobuf:\"bytes,3,rep,name=intercepts,proto3\" json:\"intercepts,omitempty\"`\n\tAgents        []*AgentInfo           `protobuf:\"bytes,4,rep,name=agents,proto3\" json:\"agents,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ReconnectClientRequest) Reset() {\n\t*x = ReconnectClientRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReconnectClientRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReconnectClientRequest) ProtoMessage() {}\n\nfunc (x *ReconnectClientRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReconnectClientRequest.ProtoReflect.Descriptor instead.\nfunc (*ReconnectClientRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *ReconnectClientRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *ReconnectClientRequest) GetClient() *ClientInfo {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn nil\n}\n\nfunc (x *ReconnectClientRequest) GetIntercepts() []*InterceptInfo {\n\tif x != nil {\n\t\treturn x.Intercepts\n\t}\n\treturn nil\n}\n\nfunc (x *ReconnectClientRequest) GetAgents() []*AgentInfo {\n\tif x != nil {\n\t\treturn x.Agents\n\t}\n\treturn nil\n}\n\ntype SessionInfo struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tSessionId        string                 `protobuf:\"bytes,1,opt,name=session_id,json=sessionId,proto3\" json:\"session_id,omitempty\"`\n\tManagerInstallId string                 `protobuf:\"bytes,2,opt,name=manager_install_id,json=managerInstallId,proto3\" json:\"manager_install_id,omitempty\"`\n\tInstallId        *string                `protobuf:\"bytes,3,opt,name=install_id,json=installId,proto3,oneof\" json:\"install_id,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *SessionInfo) Reset() {\n\t*x = SessionInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *SessionInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*SessionInfo) ProtoMessage() {}\n\nfunc (x *SessionInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use SessionInfo.ProtoReflect.Descriptor instead.\nfunc (*SessionInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *SessionInfo) GetSessionId() string {\n\tif x != nil {\n\t\treturn x.SessionId\n\t}\n\treturn \"\"\n}\n\nfunc (x *SessionInfo) GetManagerInstallId() string {\n\tif x != nil {\n\t\treturn x.ManagerInstallId\n\t}\n\treturn \"\"\n}\n\nfunc (x *SessionInfo) GetInstallId() string {\n\tif x != nil && x.InstallId != nil {\n\t\treturn *x.InstallId\n\t}\n\treturn \"\"\n}\n\ntype AgentsRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tNamespaces    []string               `protobuf:\"bytes,2,rep,name=namespaces,proto3\" json:\"namespaces,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentsRequest) Reset() {\n\t*x = AgentsRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentsRequest) ProtoMessage() {}\n\nfunc (x *AgentsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentsRequest.ProtoReflect.Descriptor instead.\nfunc (*AgentsRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *AgentsRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *AgentsRequest) GetNamespaces() []string {\n\tif x != nil {\n\t\treturn x.Namespaces\n\t}\n\treturn nil\n}\n\ntype AgentInfoSnapshot struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAgents        []*AgentInfo           `protobuf:\"bytes,1,rep,name=agents,proto3\" json:\"agents,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentInfoSnapshot) Reset() {\n\t*x = AgentInfoSnapshot{}\n\tmi := &file_manager_manager_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentInfoSnapshot) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentInfoSnapshot) ProtoMessage() {}\n\nfunc (x *AgentInfoSnapshot) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentInfoSnapshot.ProtoReflect.Descriptor instead.\nfunc (*AgentInfoSnapshot) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *AgentInfoSnapshot) GetAgents() []*AgentInfo {\n\tif x != nil {\n\t\treturn x.Agents\n\t}\n\treturn nil\n}\n\ntype AgentInfoDelta struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUpserts       map[string]*AgentInfo  `protobuf:\"bytes,1,rep,name=upserts,proto3\" json:\"upserts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tRemovals      []string               `protobuf:\"bytes,2,rep,name=removals,proto3\" json:\"removals,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentInfoDelta) Reset() {\n\t*x = AgentInfoDelta{}\n\tmi := &file_manager_manager_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentInfoDelta) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentInfoDelta) ProtoMessage() {}\n\nfunc (x *AgentInfoDelta) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentInfoDelta.ProtoReflect.Descriptor instead.\nfunc (*AgentInfoDelta) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *AgentInfoDelta) GetUpserts() map[string]*AgentInfo {\n\tif x != nil {\n\t\treturn x.Upserts\n\t}\n\treturn nil\n}\n\nfunc (x *AgentInfoDelta) GetRemovals() []string {\n\tif x != nil {\n\t\treturn x.Removals\n\t}\n\treturn nil\n}\n\ntype InterceptInfoSnapshot struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tIntercepts    []*InterceptInfo       `protobuf:\"bytes,1,rep,name=intercepts,proto3\" json:\"intercepts,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *InterceptInfoSnapshot) Reset() {\n\t*x = InterceptInfoSnapshot{}\n\tmi := &file_manager_manager_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *InterceptInfoSnapshot) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InterceptInfoSnapshot) ProtoMessage() {}\n\nfunc (x *InterceptInfoSnapshot) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InterceptInfoSnapshot.ProtoReflect.Descriptor instead.\nfunc (*InterceptInfoSnapshot) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *InterceptInfoSnapshot) GetIntercepts() []*InterceptInfo {\n\tif x != nil {\n\t\treturn x.Intercepts\n\t}\n\treturn nil\n}\n\ntype InterceptInfoDelta struct {\n\tstate         protoimpl.MessageState    `protogen:\"open.v1\"`\n\tUpserts       map[string]*InterceptInfo `protobuf:\"bytes,1,rep,name=upserts,proto3\" json:\"upserts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tRemovals      []string                  `protobuf:\"bytes,2,rep,name=removals,proto3\" json:\"removals,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *InterceptInfoDelta) Reset() {\n\t*x = InterceptInfoDelta{}\n\tmi := &file_manager_manager_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *InterceptInfoDelta) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*InterceptInfoDelta) ProtoMessage() {}\n\nfunc (x *InterceptInfoDelta) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use InterceptInfoDelta.ProtoReflect.Descriptor instead.\nfunc (*InterceptInfoDelta) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *InterceptInfoDelta) GetUpserts() map[string]*InterceptInfo {\n\tif x != nil {\n\t\treturn x.Upserts\n\t}\n\treturn nil\n}\n\nfunc (x *InterceptInfoDelta) GetRemovals() []string {\n\tif x != nil {\n\t\treturn x.Removals\n\t}\n\treturn nil\n}\n\ntype CreateInterceptRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tInterceptSpec *InterceptSpec         `protobuf:\"bytes,2,opt,name=intercept_spec,json=interceptSpec,proto3\" json:\"intercept_spec,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreateInterceptRequest) Reset() {\n\t*x = CreateInterceptRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateInterceptRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateInterceptRequest) ProtoMessage() {}\n\nfunc (x *CreateInterceptRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[13]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateInterceptRequest.ProtoReflect.Descriptor instead.\nfunc (*CreateInterceptRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *CreateInterceptRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *CreateInterceptRequest) GetInterceptSpec() *InterceptSpec {\n\tif x != nil {\n\t\treturn x.InterceptSpec\n\t}\n\treturn nil\n}\n\ntype EnsureAgentRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tName          string                 `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *EnsureAgentRequest) Reset() {\n\t*x = EnsureAgentRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *EnsureAgentRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EnsureAgentRequest) ProtoMessage() {}\n\nfunc (x *EnsureAgentRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[14]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EnsureAgentRequest.ProtoReflect.Descriptor instead.\nfunc (*EnsureAgentRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *EnsureAgentRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *EnsureAgentRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype PreparedIntercept struct {\n\tstate           protoimpl.MessageState `protogen:\"open.v1\"`\n\tError           string                 `protobuf:\"bytes,1,opt,name=error,proto3\" json:\"error,omitempty\"`\n\tErrorCategory   int32                  `protobuf:\"varint,2,opt,name=error_category,json=errorCategory,proto3\" json:\"error_category,omitempty\"`\n\tNamespace       string                 `protobuf:\"bytes,3,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tServiceUid      string                 `protobuf:\"bytes,4,opt,name=service_uid,json=serviceUid,proto3\" json:\"service_uid,omitempty\"`\n\tServiceName     string                 `protobuf:\"bytes,5,opt,name=service_name,json=serviceName,proto3\" json:\"service_name,omitempty\"`\n\tServicePortName string                 `protobuf:\"bytes,6,opt,name=service_port_name,json=servicePortName,proto3\" json:\"service_port_name,omitempty\"`\n\tServicePort     int32                  `protobuf:\"varint,7,opt,name=service_port,json=servicePort,proto3\" json:\"service_port,omitempty\"`\n\tWorkloadKind    string                 `protobuf:\"bytes,8,opt,name=workload_kind,json=workloadKind,proto3\" json:\"workload_kind,omitempty\"`\n\tAgentImage      string                 `protobuf:\"bytes,9,opt,name=agent_image,json=agentImage,proto3\" json:\"agent_image,omitempty\"`\n\tProtocol        string                 `protobuf:\"bytes,10,opt,name=protocol,proto3\" json:\"protocol,omitempty\"` // TCP or UDP\n\tContainerName   string                 `protobuf:\"bytes,11,opt,name=container_name,json=containerName,proto3\" json:\"container_name,omitempty\"`\n\tContainerPort   int32                  `protobuf:\"varint,12,opt,name=container_port,json=containerPort,proto3\" json:\"container_port,omitempty\"`\n\tPodPorts        []string               `protobuf:\"bytes,13,rep,name=pod_ports,json=podPorts,proto3\" json:\"pod_ports,omitempty\"`\n\tunknownFields   protoimpl.UnknownFields\n\tsizeCache       protoimpl.SizeCache\n}\n\nfunc (x *PreparedIntercept) Reset() {\n\t*x = PreparedIntercept{}\n\tmi := &file_manager_manager_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *PreparedIntercept) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*PreparedIntercept) ProtoMessage() {}\n\nfunc (x *PreparedIntercept) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[15]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use PreparedIntercept.ProtoReflect.Descriptor instead.\nfunc (*PreparedIntercept) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *PreparedIntercept) GetError() string {\n\tif x != nil {\n\t\treturn x.Error\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetErrorCategory() int32 {\n\tif x != nil {\n\t\treturn x.ErrorCategory\n\t}\n\treturn 0\n}\n\nfunc (x *PreparedIntercept) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetServiceUid() string {\n\tif x != nil {\n\t\treturn x.ServiceUid\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetServiceName() string {\n\tif x != nil {\n\t\treturn x.ServiceName\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetServicePortName() string {\n\tif x != nil {\n\t\treturn x.ServicePortName\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetServicePort() int32 {\n\tif x != nil {\n\t\treturn x.ServicePort\n\t}\n\treturn 0\n}\n\nfunc (x *PreparedIntercept) GetWorkloadKind() string {\n\tif x != nil {\n\t\treturn x.WorkloadKind\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetAgentImage() string {\n\tif x != nil {\n\t\treturn x.AgentImage\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetProtocol() string {\n\tif x != nil {\n\t\treturn x.Protocol\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetContainerName() string {\n\tif x != nil {\n\t\treturn x.ContainerName\n\t}\n\treturn \"\"\n}\n\nfunc (x *PreparedIntercept) GetContainerPort() int32 {\n\tif x != nil {\n\t\treturn x.ContainerPort\n\t}\n\treturn 0\n}\n\nfunc (x *PreparedIntercept) GetPodPorts() []string {\n\tif x != nil {\n\t\treturn x.PodPorts\n\t}\n\treturn nil\n}\n\ntype RemoveInterceptRequest2 struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tName          string                 `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemoveInterceptRequest2) Reset() {\n\t*x = RemoveInterceptRequest2{}\n\tmi := &file_manager_manager_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemoveInterceptRequest2) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemoveInterceptRequest2) ProtoMessage() {}\n\nfunc (x *RemoveInterceptRequest2) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[16]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemoveInterceptRequest2.ProtoReflect.Descriptor instead.\nfunc (*RemoveInterceptRequest2) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *RemoveInterceptRequest2) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *RemoveInterceptRequest2) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype GetInterceptRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tName          string                 `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetInterceptRequest) Reset() {\n\t*x = GetInterceptRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetInterceptRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetInterceptRequest) ProtoMessage() {}\n\nfunc (x *GetInterceptRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[17]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetInterceptRequest.ProtoReflect.Descriptor instead.\nfunc (*GetInterceptRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *GetInterceptRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *GetInterceptRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype ReviewInterceptRequest struct {\n\tstate       protoimpl.MessageState   `protogen:\"open.v1\"`\n\tSession     *SessionInfo             `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tId          string                   `protobuf:\"bytes,2,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tDisposition InterceptDispositionType `protobuf:\"varint,3,opt,name=disposition,proto3,enum=telepresence.manager.InterceptDispositionType\" json:\"disposition,omitempty\"`\n\tMessage     string                   `protobuf:\"bytes,4,opt,name=message,proto3\" json:\"message,omitempty\"`\n\t// pod IP and sftp port to use when doing sshfs mounts\n\tPodIp    string `protobuf:\"bytes,5,opt,name=pod_ip,json=podIp,proto3\" json:\"pod_ip,omitempty\"`\n\tSftpPort int32  `protobuf:\"varint,6,opt,name=sftp_port,json=sftpPort,proto3\" json:\"sftp_port,omitempty\"`\n\tFtpPort  int32  `protobuf:\"varint,12,opt,name=ftp_port,json=ftpPort,proto3\" json:\"ftp_port,omitempty\"`\n\t// The directory where the intercept mounts can be found in the agent\n\tMountPoint string `protobuf:\"bytes,10,opt,name=mount_point,json=mountPoint,proto3\" json:\"mount_point,omitempty\"`\n\t// A human-friendly description of what the\n\t// InterceptSpec.mechanism_args say.\n\tMechanismArgsDesc string `protobuf:\"bytes,7,opt,name=mechanism_args_desc,json=mechanismArgsDesc,proto3\" json:\"mechanism_args_desc,omitempty\"`\n\t// The environment of the intercepted container\n\tEnvironment map[string]string `protobuf:\"bytes,11,rep,name=environment,proto3\" json:\"environment,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// Map of mount path -> MountPolicy of the engaged container\n\tMounts        map[string]int32 `protobuf:\"bytes,13,rep,name=mounts,proto3\" json:\"mounts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"varint,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ReviewInterceptRequest) Reset() {\n\t*x = ReviewInterceptRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[18]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ReviewInterceptRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ReviewInterceptRequest) ProtoMessage() {}\n\nfunc (x *ReviewInterceptRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[18]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ReviewInterceptRequest.ProtoReflect.Descriptor instead.\nfunc (*ReviewInterceptRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *ReviewInterceptRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *ReviewInterceptRequest) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *ReviewInterceptRequest) GetDisposition() InterceptDispositionType {\n\tif x != nil {\n\t\treturn x.Disposition\n\t}\n\treturn InterceptDispositionType_UNSPECIFIED\n}\n\nfunc (x *ReviewInterceptRequest) GetMessage() string {\n\tif x != nil {\n\t\treturn x.Message\n\t}\n\treturn \"\"\n}\n\nfunc (x *ReviewInterceptRequest) GetPodIp() string {\n\tif x != nil {\n\t\treturn x.PodIp\n\t}\n\treturn \"\"\n}\n\nfunc (x *ReviewInterceptRequest) GetSftpPort() int32 {\n\tif x != nil {\n\t\treturn x.SftpPort\n\t}\n\treturn 0\n}\n\nfunc (x *ReviewInterceptRequest) GetFtpPort() int32 {\n\tif x != nil {\n\t\treturn x.FtpPort\n\t}\n\treturn 0\n}\n\nfunc (x *ReviewInterceptRequest) GetMountPoint() string {\n\tif x != nil {\n\t\treturn x.MountPoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *ReviewInterceptRequest) GetMechanismArgsDesc() string {\n\tif x != nil {\n\t\treturn x.MechanismArgsDesc\n\t}\n\treturn \"\"\n}\n\nfunc (x *ReviewInterceptRequest) GetEnvironment() map[string]string {\n\tif x != nil {\n\t\treturn x.Environment\n\t}\n\treturn nil\n}\n\nfunc (x *ReviewInterceptRequest) GetMounts() map[string]int32 {\n\tif x != nil {\n\t\treturn x.Mounts\n\t}\n\treturn nil\n}\n\ntype RemainRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tLastActivity  *timestamppb.Timestamp `protobuf:\"bytes,3,opt,name=last_activity,json=lastActivity,proto3\" json:\"last_activity,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RemainRequest) Reset() {\n\t*x = RemainRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[19]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RemainRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RemainRequest) ProtoMessage() {}\n\nfunc (x *RemainRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[19]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RemainRequest.ProtoReflect.Descriptor instead.\nfunc (*RemainRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *RemainRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *RemainRequest) GetLastActivity() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.LastActivity\n\t}\n\treturn nil\n}\n\ntype LogLevelRequest struct {\n\tstate    protoimpl.MessageState `protogen:\"open.v1\"`\n\tLogLevel string                 `protobuf:\"bytes,1,opt,name=log_level,json=logLevel,proto3\" json:\"log_level,omitempty\"`\n\t// The time that this log-level will be in effect before\n\t// falling back to the configured log-level.\n\tDuration      *durationpb.Duration `protobuf:\"bytes,2,opt,name=duration,proto3\" json:\"duration,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LogLevelRequest) Reset() {\n\t*x = LogLevelRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[20]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LogLevelRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LogLevelRequest) ProtoMessage() {}\n\nfunc (x *LogLevelRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[20]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LogLevelRequest.ProtoReflect.Descriptor instead.\nfunc (*LogLevelRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *LogLevelRequest) GetLogLevel() string {\n\tif x != nil {\n\t\treturn x.LogLevel\n\t}\n\treturn \"\"\n}\n\nfunc (x *LogLevelRequest) GetDuration() *durationpb.Duration {\n\tif x != nil {\n\t\treturn x.Duration\n\t}\n\treturn nil\n}\n\n// Deprecated.\ntype GetLogsRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Whether or not logs from the traffic-manager are desired.\n\tTrafficManager bool `protobuf:\"varint,1,opt,name=traffic_manager,json=trafficManager,proto3\" json:\"traffic_manager,omitempty\"`\n\t// The traffic-agent(s) logs are desired from. Can be `all`, `False`,\n\t// or substring to filter based on pod names.\n\tAgents string `protobuf:\"bytes,2,opt,name=agents,proto3\" json:\"agents,omitempty\"`\n\t// Whether or not to get the pod yaml deployed to the cluster.\n\tGetPodYaml    bool `protobuf:\"varint,3,opt,name=get_pod_yaml,json=getPodYaml,proto3\" json:\"get_pod_yaml,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetLogsRequest) Reset() {\n\t*x = GetLogsRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[21]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetLogsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetLogsRequest) ProtoMessage() {}\n\nfunc (x *GetLogsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[21]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetLogsRequest.ProtoReflect.Descriptor instead.\nfunc (*GetLogsRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *GetLogsRequest) GetTrafficManager() bool {\n\tif x != nil {\n\t\treturn x.TrafficManager\n\t}\n\treturn false\n}\n\nfunc (x *GetLogsRequest) GetAgents() string {\n\tif x != nil {\n\t\treturn x.Agents\n\t}\n\treturn \"\"\n}\n\nfunc (x *GetLogsRequest) GetGetPodYaml() bool {\n\tif x != nil {\n\t\treturn x.GetPodYaml\n\t}\n\treturn false\n}\n\n// Deprecated.\ntype LogsResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The map contains assocations between <podName.namespace> and the logs\n\t// from that pod.\n\tPodLogs map[string]string `protobuf:\"bytes,1,rep,name=pod_logs,json=podLogs,proto3\" json:\"pod_logs,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// Errors encountered when getting logs from the traffic-manager\n\t// and/or traffic-agents.\n\tErrMsg string `protobuf:\"bytes,2,opt,name=err_msg,json=errMsg,proto3\" json:\"err_msg,omitempty\"`\n\t// The map contains assocations between <podName.namespace> and the pod's\n\t// yaml.\n\tPodYaml       map[string]string `protobuf:\"bytes,3,rep,name=pod_yaml,json=podYaml,proto3\" json:\"pod_yaml,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LogsResponse) Reset() {\n\t*x = LogsResponse{}\n\tmi := &file_manager_manager_proto_msgTypes[22]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LogsResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LogsResponse) ProtoMessage() {}\n\nfunc (x *LogsResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[22]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LogsResponse.ProtoReflect.Descriptor instead.\nfunc (*LogsResponse) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *LogsResponse) GetPodLogs() map[string]string {\n\tif x != nil {\n\t\treturn x.PodLogs\n\t}\n\treturn nil\n}\n\nfunc (x *LogsResponse) GetErrMsg() string {\n\tif x != nil {\n\t\treturn x.ErrMsg\n\t}\n\treturn \"\"\n}\n\nfunc (x *LogsResponse) GetPodYaml() map[string]string {\n\tif x != nil {\n\t\treturn x.PodYaml\n\t}\n\treturn nil\n}\n\ntype TelepresenceAPIInfo struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The port that the TelepresenceAPI is using, or 0 if it's not enabled\n\tPort          int32 `protobuf:\"varint,1,opt,name=port,proto3\" json:\"port,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TelepresenceAPIInfo) Reset() {\n\t*x = TelepresenceAPIInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[23]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TelepresenceAPIInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TelepresenceAPIInfo) ProtoMessage() {}\n\nfunc (x *TelepresenceAPIInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[23]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TelepresenceAPIInfo.ProtoReflect.Descriptor instead.\nfunc (*TelepresenceAPIInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *TelepresenceAPIInfo) GetPort() int32 {\n\tif x != nil {\n\t\treturn x.Port\n\t}\n\treturn 0\n}\n\n// VersionInfo2 is different than telepresence.common.VersionInfo in\n// that it is limited to just name and version.\ntype VersionInfo2 struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tVersion       string                 `protobuf:\"bytes,2,opt,name=version,proto3\" json:\"version,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VersionInfo2) Reset() {\n\t*x = VersionInfo2{}\n\tmi := &file_manager_manager_proto_msgTypes[24]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionInfo2) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionInfo2) ProtoMessage() {}\n\nfunc (x *VersionInfo2) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[24]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VersionInfo2.ProtoReflect.Descriptor instead.\nfunc (*VersionInfo2) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *VersionInfo2) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *VersionInfo2) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\n// TunnelMessage is a message sent over a Tunnel. First byte indicates type of message\ntype TunnelMessage struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPayload       []byte                 `protobuf:\"bytes,1,opt,name=payload,proto3\" json:\"payload,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TunnelMessage) Reset() {\n\t*x = TunnelMessage{}\n\tmi := &file_manager_manager_proto_msgTypes[25]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TunnelMessage) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TunnelMessage) ProtoMessage() {}\n\nfunc (x *TunnelMessage) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[25]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TunnelMessage.ProtoReflect.Descriptor instead.\nfunc (*TunnelMessage) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *TunnelMessage) GetPayload() []byte {\n\tif x != nil {\n\t\treturn x.Payload\n\t}\n\treturn nil\n}\n\ntype DialRequest struct {\n\tstate            protoimpl.MessageState `protogen:\"open.v1\"`\n\tConnId           []byte                 `protobuf:\"bytes,1,opt,name=conn_id,json=connId,proto3\" json:\"conn_id,omitempty\"`\n\tRoundtripLatency int64                  `protobuf:\"varint,2,opt,name=roundtrip_latency,json=roundtripLatency,proto3\" json:\"roundtrip_latency,omitempty\"`\n\tDialTimeout      int64                  `protobuf:\"varint,3,opt,name=dial_timeout,json=dialTimeout,proto3\" json:\"dial_timeout,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *DialRequest) Reset() {\n\t*x = DialRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[26]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DialRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DialRequest) ProtoMessage() {}\n\nfunc (x *DialRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[26]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DialRequest.ProtoReflect.Descriptor instead.\nfunc (*DialRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *DialRequest) GetConnId() []byte {\n\tif x != nil {\n\t\treturn x.ConnId\n\t}\n\treturn nil\n}\n\nfunc (x *DialRequest) GetRoundtripLatency() int64 {\n\tif x != nil {\n\t\treturn x.RoundtripLatency\n\t}\n\treturn 0\n}\n\nfunc (x *DialRequest) GetDialTimeout() int64 {\n\tif x != nil {\n\t\treturn x.DialTimeout\n\t}\n\treturn 0\n}\n\ntype LookupRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Client session\n\tSession *SessionInfo `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\t// The name to use for the LookupIP request.\n\tName          string `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LookupRequest) Reset() {\n\t*x = LookupRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[27]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LookupRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LookupRequest) ProtoMessage() {}\n\nfunc (x *LookupRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[27]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LookupRequest.ProtoReflect.Descriptor instead.\nfunc (*LookupRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *LookupRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *LookupRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype LookupResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// List of found IPs in netip.Addr binary form.\n\tIps           [][]byte `protobuf:\"bytes,1,rep,name=ips,proto3\" json:\"ips,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *LookupResponse) Reset() {\n\t*x = LookupResponse{}\n\tmi := &file_manager_manager_proto_msgTypes[28]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *LookupResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*LookupResponse) ProtoMessage() {}\n\nfunc (x *LookupResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[28]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use LookupResponse.ProtoReflect.Descriptor instead.\nfunc (*LookupResponse) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *LookupResponse) GetIps() [][]byte {\n\tif x != nil {\n\t\treturn x.Ips\n\t}\n\treturn nil\n}\n\n// LookupHost request sent from a client\ntype DNSRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Client session\n\tSession       *SessionInfo `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tName          string       `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tType          uint32       `protobuf:\"varint,3,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DNSRequest) Reset() {\n\t*x = DNSRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[29]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DNSRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DNSRequest) ProtoMessage() {}\n\nfunc (x *DNSRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[29]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DNSRequest.ProtoReflect.Descriptor instead.\nfunc (*DNSRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{29}\n}\n\nfunc (x *DNSRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *DNSRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *DNSRequest) GetType() uint32 {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn 0\n}\n\ntype DNSResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// DNS return code\n\tRCode int32 `protobuf:\"varint,1,opt,name=r_code,json=rCode,proto3\" json:\"r_code,omitempty\"`\n\t// rrs is an array of packed RR records\n\tRrs           []byte `protobuf:\"bytes,2,opt,name=rrs,proto3\" json:\"rrs,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DNSResponse) Reset() {\n\t*x = DNSResponse{}\n\tmi := &file_manager_manager_proto_msgTypes[30]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DNSResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DNSResponse) ProtoMessage() {}\n\nfunc (x *DNSResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[30]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DNSResponse.ProtoReflect.Descriptor instead.\nfunc (*DNSResponse) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *DNSResponse) GetRCode() int32 {\n\tif x != nil {\n\t\treturn x.RCode\n\t}\n\treturn 0\n}\n\nfunc (x *DNSResponse) GetRrs() []byte {\n\tif x != nil {\n\t\treturn x.Rrs\n\t}\n\treturn nil\n}\n\ntype DNSAgentResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Agent session\n\tSession *SessionInfo `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\t// DNSRequest is the request that this is a response to\n\tRequest *DNSRequest `protobuf:\"bytes,2,opt,name=request,proto3\" json:\"request,omitempty\"`\n\t// The response, which might be nil in case no address was found\n\tResponse      *DNSResponse `protobuf:\"bytes,3,opt,name=response,proto3\" json:\"response,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DNSAgentResponse) Reset() {\n\t*x = DNSAgentResponse{}\n\tmi := &file_manager_manager_proto_msgTypes[31]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DNSAgentResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DNSAgentResponse) ProtoMessage() {}\n\nfunc (x *DNSAgentResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[31]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DNSAgentResponse.ProtoReflect.Descriptor instead.\nfunc (*DNSAgentResponse) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{31}\n}\n\nfunc (x *DNSAgentResponse) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *DNSAgentResponse) GetRequest() *DNSRequest {\n\tif x != nil {\n\t\treturn x.Request\n\t}\n\treturn nil\n}\n\nfunc (x *DNSAgentResponse) GetResponse() *DNSResponse {\n\tif x != nil {\n\t\treturn x.Response\n\t}\n\treturn nil\n}\n\n// IPNet is a subnet. e.g. 10.43.0.0/16\ntype IPNet struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tIp            []byte                 `protobuf:\"bytes,1,opt,name=ip,proto3\" json:\"ip,omitempty\"`\n\tMask          int32                  `protobuf:\"varint,2,opt,name=mask,proto3\" json:\"mask,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *IPNet) Reset() {\n\t*x = IPNet{}\n\tmi := &file_manager_manager_proto_msgTypes[32]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IPNet) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IPNet) ProtoMessage() {}\n\nfunc (x *IPNet) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[32]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use IPNet.ProtoReflect.Descriptor instead.\nfunc (*IPNet) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{32}\n}\n\nfunc (x *IPNet) GetIp() []byte {\n\tif x != nil {\n\t\treturn x.Ip\n\t}\n\treturn nil\n}\n\nfunc (x *IPNet) GetMask() int32 {\n\tif x != nil {\n\t\treturn x.Mask\n\t}\n\treturn 0\n}\n\n// ClusterInfo contains information that the root daemon needs in order to\n// establish outbound traffic to the cluster.\ntype ClusterInfo struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The service_cidrs reported by NetworkingV1().ServiceCIDRs() in netip.Prefix binary form.\n\tServiceCidrs [][]byte `protobuf:\"bytes,1,rep,name=service_cidrs,json=serviceCidrs,proto3\" json:\"service_cidrs,omitempty\"`\n\t// service_subnet is the Kubernetes service subnet.\n\t// Deprecated: use service_cidrs\n\tServiceSubnet *IPNet `protobuf:\"bytes,2,opt,name=service_subnet,json=serviceSubnet,proto3\" json:\"service_subnet,omitempty\"`\n\t// pod_subnets are the subnets used for Kubenetes pods.\n\tPodSubnets []*IPNet `protobuf:\"bytes,3,rep,name=pod_subnets,json=podSubnets,proto3\" json:\"pod_subnets,omitempty\"`\n\t// manager_pod_ip is the ip address of the traffic manager\n\tManagerPodIp []byte `protobuf:\"bytes,5,opt,name=manager_pod_ip,json=managerPodIp,proto3\" json:\"manager_pod_ip,omitempty\"`\n\t// manager_pod_port is the port of the traffic manager\n\tManagerPodPort int32 `protobuf:\"varint,8,opt,name=manager_pod_port,json=managerPodPort,proto3\" json:\"manager_pod_port,omitempty\"`\n\t// injector_svc_ip is the ip address of the traffic manager's agent injector service\n\tInjectorSvcIp []byte `protobuf:\"bytes,9,opt,name=injector_svc_ip,json=injectorSvcIp,proto3\" json:\"injector_svc_ip,omitempty\"`\n\t// injector_svc_port is the port of the traffic manager's agent injector service\n\tInjectorSvcPort int32 `protobuf:\"varint,10,opt,name=injector_svc_port,json=injectorSvcPort,proto3\" json:\"injector_svc_port,omitempty\"`\n\t// injector_svc_host is the http host of the traffic manager's agent injector service\n\tInjectorSvcHost string `protobuf:\"bytes,11,opt,name=injector_svc_host,json=injectorSvcHost,proto3\" json:\"injector_svc_host,omitempty\"`\n\t// Router configuration\n\tRouting *Routing `protobuf:\"bytes,6,opt,name=routing,proto3\" json:\"routing,omitempty\"`\n\t// DNS configuration\n\tDns           *DNS `protobuf:\"bytes,7,opt,name=dns,proto3\" json:\"dns,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ClusterInfo) Reset() {\n\t*x = ClusterInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[33]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClusterInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClusterInfo) ProtoMessage() {}\n\nfunc (x *ClusterInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[33]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClusterInfo.ProtoReflect.Descriptor instead.\nfunc (*ClusterInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{33}\n}\n\nfunc (x *ClusterInfo) GetServiceCidrs() [][]byte {\n\tif x != nil {\n\t\treturn x.ServiceCidrs\n\t}\n\treturn nil\n}\n\nfunc (x *ClusterInfo) GetServiceSubnet() *IPNet {\n\tif x != nil {\n\t\treturn x.ServiceSubnet\n\t}\n\treturn nil\n}\n\nfunc (x *ClusterInfo) GetPodSubnets() []*IPNet {\n\tif x != nil {\n\t\treturn x.PodSubnets\n\t}\n\treturn nil\n}\n\nfunc (x *ClusterInfo) GetManagerPodIp() []byte {\n\tif x != nil {\n\t\treturn x.ManagerPodIp\n\t}\n\treturn nil\n}\n\nfunc (x *ClusterInfo) GetManagerPodPort() int32 {\n\tif x != nil {\n\t\treturn x.ManagerPodPort\n\t}\n\treturn 0\n}\n\nfunc (x *ClusterInfo) GetInjectorSvcIp() []byte {\n\tif x != nil {\n\t\treturn x.InjectorSvcIp\n\t}\n\treturn nil\n}\n\nfunc (x *ClusterInfo) GetInjectorSvcPort() int32 {\n\tif x != nil {\n\t\treturn x.InjectorSvcPort\n\t}\n\treturn 0\n}\n\nfunc (x *ClusterInfo) GetInjectorSvcHost() string {\n\tif x != nil {\n\t\treturn x.InjectorSvcHost\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClusterInfo) GetRouting() *Routing {\n\tif x != nil {\n\t\treturn x.Routing\n\t}\n\treturn nil\n}\n\nfunc (x *ClusterInfo) GetDns() *DNS {\n\tif x != nil {\n\t\treturn x.Dns\n\t}\n\treturn nil\n}\n\ntype Routing struct {\n\tstate                   protoimpl.MessageState `protogen:\"open.v1\"`\n\tAlsoProxySubnets        []*IPNet               `protobuf:\"bytes,1,rep,name=also_proxy_subnets,json=alsoProxySubnets,proto3\" json:\"also_proxy_subnets,omitempty\"`\n\tNeverProxySubnets       []*IPNet               `protobuf:\"bytes,2,rep,name=never_proxy_subnets,json=neverProxySubnets,proto3\" json:\"never_proxy_subnets,omitempty\"`\n\tAllowConflictingSubnets []*IPNet               `protobuf:\"bytes,3,rep,name=allow_conflicting_subnets,json=allowConflictingSubnets,proto3\" json:\"allow_conflicting_subnets,omitempty\"`\n\tunknownFields           protoimpl.UnknownFields\n\tsizeCache               protoimpl.SizeCache\n}\n\nfunc (x *Routing) Reset() {\n\t*x = Routing{}\n\tmi := &file_manager_manager_proto_msgTypes[34]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Routing) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Routing) ProtoMessage() {}\n\nfunc (x *Routing) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[34]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Routing.ProtoReflect.Descriptor instead.\nfunc (*Routing) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{34}\n}\n\nfunc (x *Routing) GetAlsoProxySubnets() []*IPNet {\n\tif x != nil {\n\t\treturn x.AlsoProxySubnets\n\t}\n\treturn nil\n}\n\nfunc (x *Routing) GetNeverProxySubnets() []*IPNet {\n\tif x != nil {\n\t\treturn x.NeverProxySubnets\n\t}\n\treturn nil\n}\n\nfunc (x *Routing) GetAllowConflictingSubnets() []*IPNet {\n\tif x != nil {\n\t\treturn x.AllowConflictingSubnets\n\t}\n\treturn nil\n}\n\ntype DNS struct {\n\tstate           protoimpl.MessageState `protogen:\"open.v1\"`\n\tIncludeSuffixes []string               `protobuf:\"bytes,1,rep,name=include_suffixes,json=includeSuffixes,proto3\" json:\"include_suffixes,omitempty\"`\n\tExcludeSuffixes []string               `protobuf:\"bytes,2,rep,name=exclude_suffixes,json=excludeSuffixes,proto3\" json:\"exclude_suffixes,omitempty\"`\n\t// kube_dns_ip is the IP address of the kube-dns.kube-system service,\n\t// Deprecated: No longer used by clients >= 2.8.0\n\tKubeIp []byte `protobuf:\"bytes,3,opt,name=kube_ip,json=kubeIp,proto3\" json:\"kube_ip,omitempty\"`\n\t// cluster_domain is the domain of the cluster, ending with a dot, e.g. \"cluster.local.\"\n\tClusterDomain string `protobuf:\"bytes,4,opt,name=cluster_domain,json=clusterDomain,proto3\" json:\"cluster_domain,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DNS) Reset() {\n\t*x = DNS{}\n\tmi := &file_manager_manager_proto_msgTypes[35]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DNS) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DNS) ProtoMessage() {}\n\nfunc (x *DNS) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[35]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DNS.ProtoReflect.Descriptor instead.\nfunc (*DNS) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{35}\n}\n\nfunc (x *DNS) GetIncludeSuffixes() []string {\n\tif x != nil {\n\t\treturn x.IncludeSuffixes\n\t}\n\treturn nil\n}\n\nfunc (x *DNS) GetExcludeSuffixes() []string {\n\tif x != nil {\n\t\treturn x.ExcludeSuffixes\n\t}\n\treturn nil\n}\n\nfunc (x *DNS) GetKubeIp() []byte {\n\tif x != nil {\n\t\treturn x.KubeIp\n\t}\n\treturn nil\n}\n\nfunc (x *DNS) GetClusterDomain() string {\n\tif x != nil {\n\t\treturn x.ClusterDomain\n\t}\n\treturn \"\"\n}\n\ntype CLIConfig struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// config_yaml is a yaml blob containing the client config.\n\tConfigYaml    []byte `protobuf:\"bytes,1,opt,name=config_yaml,json=configYaml,proto3\" json:\"config_yaml,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CLIConfig) Reset() {\n\t*x = CLIConfig{}\n\tmi := &file_manager_manager_proto_msgTypes[36]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CLIConfig) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CLIConfig) ProtoMessage() {}\n\nfunc (x *CLIConfig) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[36]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CLIConfig.ProtoReflect.Descriptor instead.\nfunc (*CLIConfig) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{36}\n}\n\nfunc (x *CLIConfig) GetConfigYaml() []byte {\n\tif x != nil {\n\t\treturn x.ConfigYaml\n\t}\n\treturn nil\n}\n\ntype AgentImageFQN struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tFQN           string                 `protobuf:\"bytes,1,opt,name=f_q_n,json=fQN,proto3\" json:\"f_q_n,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentImageFQN) Reset() {\n\t*x = AgentImageFQN{}\n\tmi := &file_manager_manager_proto_msgTypes[37]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentImageFQN) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentImageFQN) ProtoMessage() {}\n\nfunc (x *AgentImageFQN) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[37]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentImageFQN.ProtoReflect.Descriptor instead.\nfunc (*AgentImageFQN) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{37}\n}\n\nfunc (x *AgentImageFQN) GetFQN() string {\n\tif x != nil {\n\t\treturn x.FQN\n\t}\n\treturn \"\"\n}\n\ntype AgentPodInfo struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPodId         string                 `protobuf:\"bytes,7,opt,name=pod_id,json=podId,proto3\" json:\"pod_id,omitempty\"`\n\tPodName       string                 `protobuf:\"bytes,1,opt,name=pod_name,json=podName,proto3\" json:\"pod_name,omitempty\"`\n\tNamespace     string                 `protobuf:\"bytes,2,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tPodIp         []byte                 `protobuf:\"bytes,3,opt,name=pod_ip,json=podIp,proto3\" json:\"pod_ip,omitempty\"`\n\tApiPort       int32                  `protobuf:\"varint,4,opt,name=api_port,json=apiPort,proto3\" json:\"api_port,omitempty\"`\n\tIntercepted   bool                   `protobuf:\"varint,5,opt,name=intercepted,proto3\" json:\"intercepted,omitempty\"`\n\tWorkloadName  string                 `protobuf:\"bytes,6,opt,name=workload_name,json=workloadName,proto3\" json:\"workload_name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentPodInfo) Reset() {\n\t*x = AgentPodInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[38]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentPodInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentPodInfo) ProtoMessage() {}\n\nfunc (x *AgentPodInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[38]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentPodInfo.ProtoReflect.Descriptor instead.\nfunc (*AgentPodInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{38}\n}\n\nfunc (x *AgentPodInfo) GetPodId() string {\n\tif x != nil {\n\t\treturn x.PodId\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentPodInfo) GetPodName() string {\n\tif x != nil {\n\t\treturn x.PodName\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentPodInfo) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentPodInfo) GetPodIp() []byte {\n\tif x != nil {\n\t\treturn x.PodIp\n\t}\n\treturn nil\n}\n\nfunc (x *AgentPodInfo) GetApiPort() int32 {\n\tif x != nil {\n\t\treturn x.ApiPort\n\t}\n\treturn 0\n}\n\nfunc (x *AgentPodInfo) GetIntercepted() bool {\n\tif x != nil {\n\t\treturn x.Intercepted\n\t}\n\treturn false\n}\n\nfunc (x *AgentPodInfo) GetWorkloadName() string {\n\tif x != nil {\n\t\treturn x.WorkloadName\n\t}\n\treturn \"\"\n}\n\ntype AgentPodInfoSnapshot struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAgents        []*AgentPodInfo        `protobuf:\"bytes,1,rep,name=agents,proto3\" json:\"agents,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentPodInfoSnapshot) Reset() {\n\t*x = AgentPodInfoSnapshot{}\n\tmi := &file_manager_manager_proto_msgTypes[39]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentPodInfoSnapshot) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentPodInfoSnapshot) ProtoMessage() {}\n\nfunc (x *AgentPodInfoSnapshot) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[39]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentPodInfoSnapshot.ProtoReflect.Descriptor instead.\nfunc (*AgentPodInfoSnapshot) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{39}\n}\n\nfunc (x *AgentPodInfoSnapshot) GetAgents() []*AgentPodInfo {\n\tif x != nil {\n\t\treturn x.Agents\n\t}\n\treturn nil\n}\n\ntype AgentPodInfoDelta struct {\n\tstate         protoimpl.MessageState   `protogen:\"open.v1\"`\n\tUpserts       map[string]*AgentPodInfo `protobuf:\"bytes,1,rep,name=upserts,proto3\" json:\"upserts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tRemovals      []string                 `protobuf:\"bytes,2,rep,name=removals,proto3\" json:\"removals,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentPodInfoDelta) Reset() {\n\t*x = AgentPodInfoDelta{}\n\tmi := &file_manager_manager_proto_msgTypes[40]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentPodInfoDelta) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentPodInfoDelta) ProtoMessage() {}\n\nfunc (x *AgentPodInfoDelta) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[40]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentPodInfoDelta.ProtoReflect.Descriptor instead.\nfunc (*AgentPodInfoDelta) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{40}\n}\n\nfunc (x *AgentPodInfoDelta) GetUpserts() map[string]*AgentPodInfo {\n\tif x != nil {\n\t\treturn x.Upserts\n\t}\n\treturn nil\n}\n\nfunc (x *AgentPodInfoDelta) GetRemovals() []string {\n\tif x != nil {\n\t\treturn x.Removals\n\t}\n\treturn nil\n}\n\ntype AgentConfigRequest struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tSession       *SessionInfo           `protobuf:\"bytes,1,opt,name=session,proto3\" json:\"session,omitempty\"`\n\tName          string                 `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentConfigRequest) Reset() {\n\t*x = AgentConfigRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[41]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentConfigRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentConfigRequest) ProtoMessage() {}\n\nfunc (x *AgentConfigRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[41]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentConfigRequest.ProtoReflect.Descriptor instead.\nfunc (*AgentConfigRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{41}\n}\n\nfunc (x *AgentConfigRequest) GetSession() *SessionInfo {\n\tif x != nil {\n\t\treturn x.Session\n\t}\n\treturn nil\n}\n\nfunc (x *AgentConfigRequest) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\ntype AgentConfigResponse struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tData          []byte                 `protobuf:\"bytes,1,opt,name=data,proto3\" json:\"data,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentConfigResponse) Reset() {\n\t*x = AgentConfigResponse{}\n\tmi := &file_manager_manager_proto_msgTypes[42]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentConfigResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentConfigResponse) ProtoMessage() {}\n\nfunc (x *AgentConfigResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[42]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentConfigResponse.ProtoReflect.Descriptor instead.\nfunc (*AgentConfigResponse) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{42}\n}\n\nfunc (x *AgentConfigResponse) GetData() []byte {\n\tif x != nil {\n\t\treturn x.Data\n\t}\n\treturn nil\n}\n\ntype TunnelMetrics struct {\n\tstate           protoimpl.MessageState `protogen:\"open.v1\"`\n\tClientSessionId string                 `protobuf:\"bytes,1,opt,name=client_session_id,json=clientSessionId,proto3\" json:\"client_session_id,omitempty\"`\n\t// Number of bytes sent from the client to the traffic-agent.\n\tIngressBytes uint64 `protobuf:\"varint,2,opt,name=ingress_bytes,json=ingressBytes,proto3\" json:\"ingress_bytes,omitempty\"`\n\t// Number of bytes sent from traffic-agent to the client.\n\tEgressBytes   uint64 `protobuf:\"varint,3,opt,name=egress_bytes,json=egressBytes,proto3\" json:\"egress_bytes,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *TunnelMetrics) Reset() {\n\t*x = TunnelMetrics{}\n\tmi := &file_manager_manager_proto_msgTypes[43]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *TunnelMetrics) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*TunnelMetrics) ProtoMessage() {}\n\nfunc (x *TunnelMetrics) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[43]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use TunnelMetrics.ProtoReflect.Descriptor instead.\nfunc (*TunnelMetrics) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{43}\n}\n\nfunc (x *TunnelMetrics) GetClientSessionId() string {\n\tif x != nil {\n\t\treturn x.ClientSessionId\n\t}\n\treturn \"\"\n}\n\nfunc (x *TunnelMetrics) GetIngressBytes() uint64 {\n\tif x != nil {\n\t\treturn x.IngressBytes\n\t}\n\treturn 0\n}\n\nfunc (x *TunnelMetrics) GetEgressBytes() uint64 {\n\tif x != nil {\n\t\treturn x.EgressBytes\n\t}\n\treturn 0\n}\n\ntype KnownWorkloadKinds struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tKinds         []WorkloadInfo_Kind    `protobuf:\"varint,1,rep,packed,name=kinds,proto3,enum=telepresence.manager.WorkloadInfo_Kind\" json:\"kinds,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *KnownWorkloadKinds) Reset() {\n\t*x = KnownWorkloadKinds{}\n\tmi := &file_manager_manager_proto_msgTypes[44]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *KnownWorkloadKinds) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*KnownWorkloadKinds) ProtoMessage() {}\n\nfunc (x *KnownWorkloadKinds) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[44]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use KnownWorkloadKinds.ProtoReflect.Descriptor instead.\nfunc (*KnownWorkloadKinds) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{44}\n}\n\nfunc (x *KnownWorkloadKinds) GetKinds() []WorkloadInfo_Kind {\n\tif x != nil {\n\t\treturn x.Kinds\n\t}\n\treturn nil\n}\n\n// WorkloadInfo contains information about a workload (typically a\n// Deployment).\ntype WorkloadInfo struct {\n\tstate            protoimpl.MessageState    `protogen:\"open.v1\"`\n\tKind             WorkloadInfo_Kind         `protobuf:\"varint,1,opt,name=kind,proto3,enum=telepresence.manager.WorkloadInfo_Kind\" json:\"kind,omitempty\"`\n\tName             string                    `protobuf:\"bytes,2,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tNamespace        string                    `protobuf:\"bytes,3,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tUid              string                    `protobuf:\"bytes,7,opt,name=uid,proto3\" json:\"uid,omitempty\"`\n\tAgentState       WorkloadInfo_AgentState   `protobuf:\"varint,4,opt,name=agent_state,json=agentState,proto3,enum=telepresence.manager.WorkloadInfo_AgentState\" json:\"agent_state,omitempty\"`\n\tInterceptClients []*WorkloadInfo_Intercept `protobuf:\"bytes,5,rep,name=intercept_clients,json=interceptClients,proto3\" json:\"intercept_clients,omitempty\"`\n\tState            WorkloadInfo_State        `protobuf:\"varint,6,opt,name=state,proto3,enum=telepresence.manager.WorkloadInfo_State\" json:\"state,omitempty\"`\n\tunknownFields    protoimpl.UnknownFields\n\tsizeCache        protoimpl.SizeCache\n}\n\nfunc (x *WorkloadInfo) Reset() {\n\t*x = WorkloadInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[45]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WorkloadInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WorkloadInfo) ProtoMessage() {}\n\nfunc (x *WorkloadInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[45]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WorkloadInfo.ProtoReflect.Descriptor instead.\nfunc (*WorkloadInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{45}\n}\n\nfunc (x *WorkloadInfo) GetKind() WorkloadInfo_Kind {\n\tif x != nil {\n\t\treturn x.Kind\n\t}\n\treturn WorkloadInfo_UNSPECIFIED\n}\n\nfunc (x *WorkloadInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *WorkloadInfo) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\nfunc (x *WorkloadInfo) GetUid() string {\n\tif x != nil {\n\t\treturn x.Uid\n\t}\n\treturn \"\"\n}\n\nfunc (x *WorkloadInfo) GetAgentState() WorkloadInfo_AgentState {\n\tif x != nil {\n\t\treturn x.AgentState\n\t}\n\treturn WorkloadInfo_NO_AGENT_UNSPECIFIED\n}\n\nfunc (x *WorkloadInfo) GetInterceptClients() []*WorkloadInfo_Intercept {\n\tif x != nil {\n\t\treturn x.InterceptClients\n\t}\n\treturn nil\n}\n\nfunc (x *WorkloadInfo) GetState() WorkloadInfo_State {\n\tif x != nil {\n\t\treturn x.State\n\t}\n\treturn WorkloadInfo_UNKNOWN_UNSPECIFIED\n}\n\ntype WorkloadEvent struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tType          WorkloadEvent_Type     `protobuf:\"varint,1,opt,name=type,proto3,enum=telepresence.manager.WorkloadEvent_Type\" json:\"type,omitempty\"`\n\tWorkload      *WorkloadInfo          `protobuf:\"bytes,2,opt,name=workload,proto3\" json:\"workload,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WorkloadEvent) Reset() {\n\t*x = WorkloadEvent{}\n\tmi := &file_manager_manager_proto_msgTypes[46]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WorkloadEvent) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WorkloadEvent) ProtoMessage() {}\n\nfunc (x *WorkloadEvent) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[46]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WorkloadEvent.ProtoReflect.Descriptor instead.\nfunc (*WorkloadEvent) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{46}\n}\n\nfunc (x *WorkloadEvent) GetType() WorkloadEvent_Type {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn WorkloadEvent_ADDED_UNSPECIFIED\n}\n\nfunc (x *WorkloadEvent) GetWorkload() *WorkloadInfo {\n\tif x != nil {\n\t\treturn x.Workload\n\t}\n\treturn nil\n}\n\n// WorkloadEventDelta contains the changes made to the subscribed namespace since\n// the time given in the timestamp. A watcher can rely on that received deltas are\n// consecutive.\ntype WorkloadEventsDelta struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The timestamp from which this delta is computed. Typically\n\t// equal to the time when the previous delta was sent.\n\tSince         *timestamppb.Timestamp `protobuf:\"bytes,1,opt,name=since,proto3\" json:\"since,omitempty\"`\n\tEvents        []*WorkloadEvent       `protobuf:\"bytes,2,rep,name=events,proto3\" json:\"events,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WorkloadEventsDelta) Reset() {\n\t*x = WorkloadEventsDelta{}\n\tmi := &file_manager_manager_proto_msgTypes[47]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WorkloadEventsDelta) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WorkloadEventsDelta) ProtoMessage() {}\n\nfunc (x *WorkloadEventsDelta) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[47]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WorkloadEventsDelta.ProtoReflect.Descriptor instead.\nfunc (*WorkloadEventsDelta) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{47}\n}\n\nfunc (x *WorkloadEventsDelta) GetSince() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.Since\n\t}\n\treturn nil\n}\n\nfunc (x *WorkloadEventsDelta) GetEvents() []*WorkloadEvent {\n\tif x != nil {\n\t\treturn x.Events\n\t}\n\treturn nil\n}\n\ntype WorkloadEventsRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The session_info identifies the client connection, and hence the\n\t// namespace for the resulting watcher.\n\tSessionInfo *SessionInfo `protobuf:\"bytes,1,opt,name=session_info,json=sessionInfo,proto3\" json:\"session_info,omitempty\"`\n\t// The timestamp from which the first delta should be computed. Set to\n\t// undefined to get a delta that contains everything.\n\tSince *timestamppb.Timestamp `protobuf:\"bytes,2,opt,name=since,proto3\" json:\"since,omitempty\"`\n\t// The namespace to watch. Must be one of the namespaces that are\n\t// managed by the traffic-manager. Defaults to the connected namespace.\n\tNamespace     string `protobuf:\"bytes,3,opt,name=namespace,proto3\" json:\"namespace,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WorkloadEventsRequest) Reset() {\n\t*x = WorkloadEventsRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[48]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WorkloadEventsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WorkloadEventsRequest) ProtoMessage() {}\n\nfunc (x *WorkloadEventsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[48]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WorkloadEventsRequest.ProtoReflect.Descriptor instead.\nfunc (*WorkloadEventsRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{48}\n}\n\nfunc (x *WorkloadEventsRequest) GetSessionInfo() *SessionInfo {\n\tif x != nil {\n\t\treturn x.SessionInfo\n\t}\n\treturn nil\n}\n\nfunc (x *WorkloadEventsRequest) GetSince() *timestamppb.Timestamp {\n\tif x != nil {\n\t\treturn x.Since\n\t}\n\treturn nil\n}\n\nfunc (x *WorkloadEventsRequest) GetNamespace() string {\n\tif x != nil {\n\t\treturn x.Namespace\n\t}\n\treturn \"\"\n}\n\ntype UninstallAgentsRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The session_info identifies the client connection, and hence the\n\t// namespace for the resulting watcher.\n\tSessionInfo *SessionInfo `protobuf:\"bytes,1,opt,name=session_info,json=sessionInfo,proto3\" json:\"session_info,omitempty\"`\n\t// The agents to install. Empty means all agents in the connected namespace.\n\tAgents        []string `protobuf:\"bytes,2,rep,name=agents,proto3\" json:\"agents,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UninstallAgentsRequest) Reset() {\n\t*x = UninstallAgentsRequest{}\n\tmi := &file_manager_manager_proto_msgTypes[49]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UninstallAgentsRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UninstallAgentsRequest) ProtoMessage() {}\n\nfunc (x *UninstallAgentsRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[49]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UninstallAgentsRequest.ProtoReflect.Descriptor instead.\nfunc (*UninstallAgentsRequest) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{49}\n}\n\nfunc (x *UninstallAgentsRequest) GetSessionInfo() *SessionInfo {\n\tif x != nil {\n\t\treturn x.SessionInfo\n\t}\n\treturn nil\n}\n\nfunc (x *UninstallAgentsRequest) GetAgents() []string {\n\tif x != nil {\n\t\treturn x.Agents\n\t}\n\treturn nil\n}\n\n// \"Mechanisms\" are the ways that an Agent can decide handle\n// incoming requests, and decide whether to send them to the\n// in-cluster service, or whether to intercept them.  The \"tcp\"\n// mechanism is the only one in Telepresence open source, and\n// handles things at the TCP-level and either intercepts all TCP\n// streams or doesn't intercept anything.  Other Agents than the\n// Telepresence one may implement more mechanisms, such as\n// Ambassador Labs' \"Service Preview\" Agent which implements the\n// \"http\" mechanism which handles th \"http\" mechanism, which handles\n// things at the HTTP-request-level and can decide to intercept\n// individual HTTP requests based on the request headers.\ntype AgentInfo_Mechanism struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tName          string                 `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`       // \"tcp\" or \"http\" or \"grpc\" or ...\n\tProduct       string                 `protobuf:\"bytes,2,opt,name=product,proto3\" json:\"product,omitempty\"` // distinguish open source, our closed source, someone else's thing\n\tVersion       string                 `protobuf:\"bytes,3,opt,name=version,proto3\" json:\"version,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentInfo_Mechanism) Reset() {\n\t*x = AgentInfo_Mechanism{}\n\tmi := &file_manager_manager_proto_msgTypes[50]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentInfo_Mechanism) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentInfo_Mechanism) ProtoMessage() {}\n\nfunc (x *AgentInfo_Mechanism) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[50]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentInfo_Mechanism.ProtoReflect.Descriptor instead.\nfunc (*AgentInfo_Mechanism) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{1, 0}\n}\n\nfunc (x *AgentInfo_Mechanism) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo_Mechanism) GetProduct() string {\n\tif x != nil {\n\t\treturn x.Product\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo_Mechanism) GetVersion() string {\n\tif x != nil {\n\t\treturn x.Version\n\t}\n\treturn \"\"\n}\n\ntype AgentInfo_ContainerInfo struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The container environment\n\tEnvironment map[string]string `protobuf:\"bytes,1,rep,name=environment,proto3\" json:\"environment,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\t// The directory where the intercept mounts can be found in the agent\n\tMountPoint string `protobuf:\"bytes,4,opt,name=mount_point,json=mountPoint,proto3\" json:\"mount_point,omitempty\"`\n\t// Map of mount -> MountPolicy\n\tMounts        map[string]int32 `protobuf:\"bytes,13,rep,name=mounts,proto3\" json:\"mounts,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"varint,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *AgentInfo_ContainerInfo) Reset() {\n\t*x = AgentInfo_ContainerInfo{}\n\tmi := &file_manager_manager_proto_msgTypes[51]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *AgentInfo_ContainerInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*AgentInfo_ContainerInfo) ProtoMessage() {}\n\nfunc (x *AgentInfo_ContainerInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[51]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use AgentInfo_ContainerInfo.ProtoReflect.Descriptor instead.\nfunc (*AgentInfo_ContainerInfo) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{1, 1}\n}\n\nfunc (x *AgentInfo_ContainerInfo) GetEnvironment() map[string]string {\n\tif x != nil {\n\t\treturn x.Environment\n\t}\n\treturn nil\n}\n\nfunc (x *AgentInfo_ContainerInfo) GetMountPoint() string {\n\tif x != nil {\n\t\treturn x.MountPoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *AgentInfo_ContainerInfo) GetMounts() map[string]int32 {\n\tif x != nil {\n\t\treturn x.Mounts\n\t}\n\treturn nil\n}\n\ntype WorkloadInfo_Intercept struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// name of intercepting client\n\tClient        string `protobuf:\"bytes,1,opt,name=client,proto3\" json:\"client,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *WorkloadInfo_Intercept) Reset() {\n\t*x = WorkloadInfo_Intercept{}\n\tmi := &file_manager_manager_proto_msgTypes[66]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *WorkloadInfo_Intercept) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*WorkloadInfo_Intercept) ProtoMessage() {}\n\nfunc (x *WorkloadInfo_Intercept) ProtoReflect() protoreflect.Message {\n\tmi := &file_manager_manager_proto_msgTypes[66]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use WorkloadInfo_Intercept.ProtoReflect.Descriptor instead.\nfunc (*WorkloadInfo_Intercept) Descriptor() ([]byte, []int) {\n\treturn file_manager_manager_proto_rawDescGZIP(), []int{45, 0}\n}\n\nfunc (x *WorkloadInfo_Intercept) GetClient() string {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn \"\"\n}\n\nvar File_manager_manager_proto protoreflect.FileDescriptor\n\nconst file_manager_manager_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x15manager/manager.proto\\x12\\x14telepresence.manager\\x1a\\x1egoogle/protobuf/duration.proto\\x1a\\x1bgoogle/protobuf/empty.proto\\x1a\\x1fgoogle/protobuf/timestamp.proto\\\"\\x97\\x01\\n\" +\n\t\"\\n\" +\n\t\"ClientInfo\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x06 \\x01(\\tR\\tnamespace\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"install_id\\x18\\x02 \\x01(\\tR\\tinstallId\\x12\\x18\\n\" +\n\t\"\\aproduct\\x18\\x03 \\x01(\\tR\\aproduct\\x12\\x18\\n\" +\n\t\"\\aversion\\x18\\x04 \\x01(\\tR\\aversionJ\\x04\\b\\x05\\x10\\x06\\\"\\xeb\\a\\n\" +\n\t\"\\tAgentInfo\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x12\\n\" +\n\t\"\\x04kind\\x18\\r \\x01(\\tR\\x04kind\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\a \\x01(\\tR\\tnamespace\\x12\\x19\\n\" +\n\t\"\\bpod_name\\x18\\b \\x01(\\tR\\apodName\\x12\\x15\\n\" +\n\t\"\\x06pod_ip\\x18\\x02 \\x01(\\tR\\x05podIp\\x12\\x17\\n\" +\n\t\"\\apod_uid\\x18\\x0e \\x01(\\tR\\x06podUid\\x12\\x19\\n\" +\n\t\"\\bapi_port\\x18\\t \\x01(\\x05R\\aapiPort\\x12\\x1b\\n\" +\n\t\"\\tsftp_port\\x18\\n\" +\n\t\" \\x01(\\x05R\\bsftpPort\\x12\\x19\\n\" +\n\t\"\\bftp_port\\x18\\v \\x01(\\x05R\\aftpPort\\x12\\x18\\n\" +\n\t\"\\aproduct\\x18\\x03 \\x01(\\tR\\aproduct\\x12\\x18\\n\" +\n\t\"\\aversion\\x18\\x04 \\x01(\\tR\\aversion\\x12I\\n\" +\n\t\"\\n\" +\n\t\"mechanisms\\x18\\x05 \\x03(\\v2).telepresence.manager.AgentInfo.MechanismR\\n\" +\n\t\"mechanisms\\x12O\\n\" +\n\t\"\\n\" +\n\t\"containers\\x18\\f \\x03(\\v2/.telepresence.manager.AgentInfo.ContainersEntryR\\n\" +\n\t\"containers\\x1aS\\n\" +\n\t\"\\tMechanism\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x18\\n\" +\n\t\"\\aproduct\\x18\\x02 \\x01(\\tR\\aproduct\\x12\\x18\\n\" +\n\t\"\\aversion\\x18\\x03 \\x01(\\tR\\aversion\\x1a\\xe0\\x02\\n\" +\n\t\"\\rContainerInfo\\x12`\\n\" +\n\t\"\\venvironment\\x18\\x01 \\x03(\\v2>.telepresence.manager.AgentInfo.ContainerInfo.EnvironmentEntryR\\venvironment\\x12\\x1f\\n\" +\n\t\"\\vmount_point\\x18\\x04 \\x01(\\tR\\n\" +\n\t\"mountPoint\\x12Q\\n\" +\n\t\"\\x06mounts\\x18\\r \\x03(\\v29.telepresence.manager.AgentInfo.ContainerInfo.MountsEntryR\\x06mounts\\x1a>\\n\" +\n\t\"\\x10EnvironmentEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\x1a9\\n\" +\n\t\"\\vMountsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\x05R\\x05value:\\x028\\x01\\x1al\\n\" +\n\t\"\\x0fContainersEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12C\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\v2-.telepresence.manager.AgentInfo.ContainerInfoR\\x05value:\\x028\\x01J\\x04\\b\\x06\\x10\\a\\\"1\\n\" +\n\t\"\\vPortMapping\\x12\\x12\\n\" +\n\t\"\\x04from\\x18\\x01 \\x01(\\x05R\\x04from\\x12\\x0e\\n\" +\n\t\"\\x02to\\x18\\x02 \\x01(\\x05R\\x02to\\\"\\x9f\\t\\n\" +\n\t\"\\rInterceptSpec\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x16\\n\" +\n\t\"\\x06client\\x18\\x02 \\x01(\\tR\\x06client\\x12\\x14\\n\" +\n\t\"\\x05agent\\x18\\x03 \\x01(\\tR\\x05agent\\x12#\\n\" +\n\t\"\\rworkload_kind\\x18\\r \\x01(\\tR\\fworkloadKind\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\b \\x01(\\tR\\tnamespace\\x12\\x1c\\n\" +\n\t\"\\tmechanism\\x18\\x04 \\x01(\\tR\\tmechanism\\x12\\x1f\\n\" +\n\t\"\\vtarget_host\\x18\\x06 \\x01(\\tR\\n\" +\n\t\"targetHost\\x12\\x1b\\n\" +\n\t\"\\tpod_ports\\x18\\x05 \\x03(\\tR\\bpodPorts\\x12\\x1f\\n\" +\n\t\"\\vlocal_ports\\x18\\x12 \\x03(\\tR\\n\" +\n\t\"localPorts\\x12'\\n\" +\n\t\"\\x0fport_identifier\\x18\\n\" +\n\t\" \\x01(\\tR\\x0eportIdentifier\\x12*\\n\" +\n\t\"\\x11service_port_name\\x18\\x13 \\x01(\\tR\\x0fservicePortName\\x12!\\n\" +\n\t\"\\fservice_port\\x18\\x14 \\x01(\\x05R\\vservicePort\\x12\\x1f\\n\" +\n\t\"\\vservice_uid\\x18\\f \\x01(\\tR\\n\" +\n\t\"serviceUid\\x12!\\n\" +\n\t\"\\fservice_name\\x18\\x0e \\x01(\\tR\\vserviceName\\x12\\x1a\\n\" +\n\t\"\\bprotocol\\x18\\x15 \\x01(\\tR\\bprotocol\\x12%\\n\" +\n\t\"\\x0econtainer_name\\x18\\x18 \\x01(\\tR\\rcontainerName\\x12%\\n\" +\n\t\"\\x0econtainer_port\\x18\\x17 \\x01(\\x05R\\rcontainerPort\\x12\\x1f\\n\" +\n\t\"\\vtarget_port\\x18\\a \\x01(\\x05R\\n\" +\n\t\"targetPort\\x12+\\n\" +\n\t\"\\x11roundtrip_latency\\x18\\x10 \\x01(\\x03R\\x10roundtripLatency\\x12!\\n\" +\n\t\"\\fdial_timeout\\x18\\x11 \\x01(\\x03R\\vdialTimeout\\x12\\x1f\\n\" +\n\t\"\\vextra_ports\\x18\\x0f \\x03(\\x05R\\n\" +\n\t\"extraPorts\\x12\\x18\\n\" +\n\t\"\\areplace\\x18\\x16 \\x01(\\bR\\areplace\\x12\\x18\\n\" +\n\t\"\\awiretap\\x18\\x1a \\x01(\\bR\\awiretap\\x12&\\n\" +\n\t\"\\x0fno_default_port\\x18\\x19 \\x01(\\bR\\rnoDefaultPort\\x12]\\n\" +\n\t\"\\x0eheader_filters\\x18\\x1b \\x03(\\v26.telepresence.manager.InterceptSpec.HeaderFiltersEntryR\\rheaderFilters\\x12!\\n\" +\n\t\"\\fpath_filters\\x18\\x1c \\x03(\\tR\\vpathFilters\\x12M\\n\" +\n\t\"\\bmetadata\\x18\\x1d \\x03(\\v21.telepresence.manager.InterceptSpec.MetadataEntryR\\bmetadata\\x12\\x1c\\n\" +\n\t\"\\tplaintext\\x18\\x1e \\x01(\\bR\\tplaintext\\x1a@\\n\" +\n\t\"\\x12HeaderFiltersEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\x1a;\\n\" +\n\t\"\\rMetadataEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01J\\x04\\b\\t\\x10\\n\" +\n\t\"J\\x04\\b\\v\\x10\\f\\\"\\x89\\a\\n\" +\n\t\"\\rInterceptInfo\\x127\\n\" +\n\t\"\\x04spec\\x18\\x01 \\x01(\\v2#.telepresence.manager.InterceptSpecR\\x04spec\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x05 \\x01(\\tR\\x02id\\x12H\\n\" +\n\t\"\\x0eclient_session\\x18\\x06 \\x01(\\v2!.telepresence.manager.SessionInfoR\\rclientSession\\x12P\\n\" +\n\t\"\\vdisposition\\x18\\x03 \\x01(\\x0e2..telepresence.manager.InterceptDispositionTypeR\\vdisposition\\x12\\x18\\n\" +\n\t\"\\amessage\\x18\\x04 \\x01(\\tR\\amessage\\x12\\x19\\n\" +\n\t\"\\bpod_name\\x18\\x13 \\x01(\\tR\\apodName\\x12\\x19\\n\" +\n\t\"\\bapi_port\\x18\\x14 \\x01(\\x05R\\aapiPort\\x12\\x15\\n\" +\n\t\"\\x06pod_ip\\x18\\n\" +\n\t\" \\x01(\\tR\\x05podIp\\x12\\x1b\\n\" +\n\t\"\\tsftp_port\\x18\\v \\x01(\\x05R\\bsftpPort\\x12\\x19\\n\" +\n\t\"\\bftp_port\\x18\\x12 \\x01(\\x05R\\aftpPort\\x12,\\n\" +\n\t\"\\x12client_mount_point\\x18\\x02 \\x01(\\tR\\x10clientMountPoint\\x12\\x1f\\n\" +\n\t\"\\vmount_point\\x18\\x10 \\x01(\\tR\\n\" +\n\t\"mountPoint\\x12.\\n\" +\n\t\"\\x13mechanism_args_desc\\x18\\f \\x01(\\tR\\x11mechanismArgsDesc\\x12V\\n\" +\n\t\"\\venvironment\\x18\\x11 \\x03(\\v24.telepresence.manager.InterceptInfo.EnvironmentEntryR\\venvironment\\x12G\\n\" +\n\t\"\\x06mounts\\x18\\x16 \\x03(\\v2/.telepresence.manager.InterceptInfo.MountsEntryR\\x06mounts\\x12;\\n\" +\n\t\"\\vmodified_at\\x18\\x15 \\x01(\\v2\\x1a.google.protobuf.TimestampR\\n\" +\n\t\"modifiedAt\\x1a>\\n\" +\n\t\"\\x10EnvironmentEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\x1a9\\n\" +\n\t\"\\vMountsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\x05R\\x05value:\\x028\\x01J\\x04\\b\\a\\x10\\bJ\\x04\\b\\t\\x10\\n\" +\n\t\"J\\x04\\b\\r\\x10\\x0eJ\\x04\\b\\x0e\\x10\\x0fJ\\x04\\b\\x0f\\x10\\x10\\\"\\x8b\\x01\\n\" +\n\t\"\\x15ReconnectAgentRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x125\\n\" +\n\t\"\\x05agent\\x18\\x02 \\x01(\\v2\\x1f.telepresence.manager.AgentInfoR\\x05agent\\\"\\x8d\\x02\\n\" +\n\t\"\\x16ReconnectClientRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x128\\n\" +\n\t\"\\x06client\\x18\\x02 \\x01(\\v2 .telepresence.manager.ClientInfoR\\x06client\\x12C\\n\" +\n\t\"\\n\" +\n\t\"intercepts\\x18\\x03 \\x03(\\v2#.telepresence.manager.InterceptInfoR\\n\" +\n\t\"intercepts\\x127\\n\" +\n\t\"\\x06agents\\x18\\x04 \\x03(\\v2\\x1f.telepresence.manager.AgentInfoR\\x06agents\\\"\\x8d\\x01\\n\" +\n\t\"\\vSessionInfo\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"session_id\\x18\\x01 \\x01(\\tR\\tsessionId\\x12,\\n\" +\n\t\"\\x12manager_install_id\\x18\\x02 \\x01(\\tR\\x10managerInstallId\\x12\\\"\\n\" +\n\t\"\\n\" +\n\t\"install_id\\x18\\x03 \\x01(\\tH\\x00R\\tinstallId\\x88\\x01\\x01B\\r\\n\" +\n\t\"\\v_install_id\\\"l\\n\" +\n\t\"\\rAgentsRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12\\x1e\\n\" +\n\t\"\\n\" +\n\t\"namespaces\\x18\\x02 \\x03(\\tR\\n\" +\n\t\"namespaces\\\"L\\n\" +\n\t\"\\x11AgentInfoSnapshot\\x127\\n\" +\n\t\"\\x06agents\\x18\\x01 \\x03(\\v2\\x1f.telepresence.manager.AgentInfoR\\x06agents\\\"\\xd6\\x01\\n\" +\n\t\"\\x0eAgentInfoDelta\\x12K\\n\" +\n\t\"\\aupserts\\x18\\x01 \\x03(\\v21.telepresence.manager.AgentInfoDelta.UpsertsEntryR\\aupserts\\x12\\x1a\\n\" +\n\t\"\\bremovals\\x18\\x02 \\x03(\\tR\\bremovals\\x1a[\\n\" +\n\t\"\\fUpsertsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x125\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\v2\\x1f.telepresence.manager.AgentInfoR\\x05value:\\x028\\x01\\\"\\\\\\n\" +\n\t\"\\x15InterceptInfoSnapshot\\x12C\\n\" +\n\t\"\\n\" +\n\t\"intercepts\\x18\\x01 \\x03(\\v2#.telepresence.manager.InterceptInfoR\\n\" +\n\t\"intercepts\\\"\\xe2\\x01\\n\" +\n\t\"\\x12InterceptInfoDelta\\x12O\\n\" +\n\t\"\\aupserts\\x18\\x01 \\x03(\\v25.telepresence.manager.InterceptInfoDelta.UpsertsEntryR\\aupserts\\x12\\x1a\\n\" +\n\t\"\\bremovals\\x18\\x02 \\x03(\\tR\\bremovals\\x1a_\\n\" +\n\t\"\\fUpsertsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x129\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\v2#.telepresence.manager.InterceptInfoR\\x05value:\\x028\\x01\\\"\\xa7\\x01\\n\" +\n\t\"\\x16CreateInterceptRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12J\\n\" +\n\t\"\\x0eintercept_spec\\x18\\x02 \\x01(\\v2#.telepresence.manager.InterceptSpecR\\rinterceptSpecJ\\x04\\b\\x03\\x10\\x04\\\"e\\n\" +\n\t\"\\x12EnsureAgentRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x02 \\x01(\\tR\\x04name\\\"\\xce\\x03\\n\" +\n\t\"\\x11PreparedIntercept\\x12\\x14\\n\" +\n\t\"\\x05error\\x18\\x01 \\x01(\\tR\\x05error\\x12%\\n\" +\n\t\"\\x0eerror_category\\x18\\x02 \\x01(\\x05R\\rerrorCategory\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x03 \\x01(\\tR\\tnamespace\\x12\\x1f\\n\" +\n\t\"\\vservice_uid\\x18\\x04 \\x01(\\tR\\n\" +\n\t\"serviceUid\\x12!\\n\" +\n\t\"\\fservice_name\\x18\\x05 \\x01(\\tR\\vserviceName\\x12*\\n\" +\n\t\"\\x11service_port_name\\x18\\x06 \\x01(\\tR\\x0fservicePortName\\x12!\\n\" +\n\t\"\\fservice_port\\x18\\a \\x01(\\x05R\\vservicePort\\x12#\\n\" +\n\t\"\\rworkload_kind\\x18\\b \\x01(\\tR\\fworkloadKind\\x12\\x1f\\n\" +\n\t\"\\vagent_image\\x18\\t \\x01(\\tR\\n\" +\n\t\"agentImage\\x12\\x1a\\n\" +\n\t\"\\bprotocol\\x18\\n\" +\n\t\" \\x01(\\tR\\bprotocol\\x12%\\n\" +\n\t\"\\x0econtainer_name\\x18\\v \\x01(\\tR\\rcontainerName\\x12%\\n\" +\n\t\"\\x0econtainer_port\\x18\\f \\x01(\\x05R\\rcontainerPort\\x12\\x1b\\n\" +\n\t\"\\tpod_ports\\x18\\r \\x03(\\tR\\bpodPorts\\\"j\\n\" +\n\t\"\\x17RemoveInterceptRequest2\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x02 \\x01(\\tR\\x04name\\\"f\\n\" +\n\t\"\\x13GetInterceptRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x02 \\x01(\\tR\\x04name\\\"\\xab\\x05\\n\" +\n\t\"\\x16ReviewInterceptRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x02 \\x01(\\tR\\x02id\\x12P\\n\" +\n\t\"\\vdisposition\\x18\\x03 \\x01(\\x0e2..telepresence.manager.InterceptDispositionTypeR\\vdisposition\\x12\\x18\\n\" +\n\t\"\\amessage\\x18\\x04 \\x01(\\tR\\amessage\\x12\\x15\\n\" +\n\t\"\\x06pod_ip\\x18\\x05 \\x01(\\tR\\x05podIp\\x12\\x1b\\n\" +\n\t\"\\tsftp_port\\x18\\x06 \\x01(\\x05R\\bsftpPort\\x12\\x19\\n\" +\n\t\"\\bftp_port\\x18\\f \\x01(\\x05R\\aftpPort\\x12\\x1f\\n\" +\n\t\"\\vmount_point\\x18\\n\" +\n\t\" \\x01(\\tR\\n\" +\n\t\"mountPoint\\x12.\\n\" +\n\t\"\\x13mechanism_args_desc\\x18\\a \\x01(\\tR\\x11mechanismArgsDesc\\x12_\\n\" +\n\t\"\\venvironment\\x18\\v \\x03(\\v2=.telepresence.manager.ReviewInterceptRequest.EnvironmentEntryR\\venvironment\\x12P\\n\" +\n\t\"\\x06mounts\\x18\\r \\x03(\\v28.telepresence.manager.ReviewInterceptRequest.MountsEntryR\\x06mounts\\x1a>\\n\" +\n\t\"\\x10EnvironmentEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\x1a9\\n\" +\n\t\"\\vMountsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\x05R\\x05value:\\x028\\x01J\\x04\\b\\b\\x10\\tJ\\x04\\b\\t\\x10\\n\" +\n\t\"\\\"\\x93\\x01\\n\" +\n\t\"\\rRemainRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12?\\n\" +\n\t\"\\rlast_activity\\x18\\x03 \\x01(\\v2\\x1a.google.protobuf.TimestampR\\flastActivityJ\\x04\\b\\x02\\x10\\x03\\\"e\\n\" +\n\t\"\\x0fLogLevelRequest\\x12\\x1b\\n\" +\n\t\"\\tlog_level\\x18\\x01 \\x01(\\tR\\blogLevel\\x125\\n\" +\n\t\"\\bduration\\x18\\x02 \\x01(\\v2\\x19.google.protobuf.DurationR\\bduration\\\"s\\n\" +\n\t\"\\x0eGetLogsRequest\\x12'\\n\" +\n\t\"\\x0ftraffic_manager\\x18\\x01 \\x01(\\bR\\x0etrafficManager\\x12\\x16\\n\" +\n\t\"\\x06agents\\x18\\x02 \\x01(\\tR\\x06agents\\x12 \\n\" +\n\t\"\\fget_pod_yaml\\x18\\x03 \\x01(\\bR\\n\" +\n\t\"getPodYaml\\\"\\xb7\\x02\\n\" +\n\t\"\\fLogsResponse\\x12J\\n\" +\n\t\"\\bpod_logs\\x18\\x01 \\x03(\\v2/.telepresence.manager.LogsResponse.PodLogsEntryR\\apodLogs\\x12\\x17\\n\" +\n\t\"\\aerr_msg\\x18\\x02 \\x01(\\tR\\x06errMsg\\x12J\\n\" +\n\t\"\\bpod_yaml\\x18\\x03 \\x03(\\v2/.telepresence.manager.LogsResponse.PodYamlEntryR\\apodYaml\\x1a:\\n\" +\n\t\"\\fPodLogsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\x1a:\\n\" +\n\t\"\\fPodYamlEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x01\\\")\\n\" +\n\t\"\\x13TelepresenceAPIInfo\\x12\\x12\\n\" +\n\t\"\\x04port\\x18\\x01 \\x01(\\x05R\\x04port\\\"<\\n\" +\n\t\"\\fVersionInfo2\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x01 \\x01(\\tR\\x04name\\x12\\x18\\n\" +\n\t\"\\aversion\\x18\\x02 \\x01(\\tR\\aversion\\\")\\n\" +\n\t\"\\rTunnelMessage\\x12\\x18\\n\" +\n\t\"\\apayload\\x18\\x01 \\x01(\\fR\\apayload\\\"|\\n\" +\n\t\"\\vDialRequest\\x12\\x17\\n\" +\n\t\"\\aconn_id\\x18\\x01 \\x01(\\fR\\x06connId\\x12+\\n\" +\n\t\"\\x11roundtrip_latency\\x18\\x02 \\x01(\\x03R\\x10roundtripLatency\\x12!\\n\" +\n\t\"\\fdial_timeout\\x18\\x03 \\x01(\\x03R\\vdialTimeoutJ\\x04\\b\\x04\\x10\\x05\\\"`\\n\" +\n\t\"\\rLookupRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x02 \\x01(\\tR\\x04name\\\"\\\"\\n\" +\n\t\"\\x0eLookupResponse\\x12\\x10\\n\" +\n\t\"\\x03ips\\x18\\x01 \\x03(\\fR\\x03ips\\\"q\\n\" +\n\t\"\\n\" +\n\t\"DNSRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x02 \\x01(\\tR\\x04name\\x12\\x12\\n\" +\n\t\"\\x04type\\x18\\x03 \\x01(\\rR\\x04type\\\"6\\n\" +\n\t\"\\vDNSResponse\\x12\\x15\\n\" +\n\t\"\\x06r_code\\x18\\x01 \\x01(\\x05R\\x05rCode\\x12\\x10\\n\" +\n\t\"\\x03rrs\\x18\\x02 \\x01(\\fR\\x03rrs\\\"\\xca\\x01\\n\" +\n\t\"\\x10DNSAgentResponse\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12:\\n\" +\n\t\"\\arequest\\x18\\x02 \\x01(\\v2 .telepresence.manager.DNSRequestR\\arequest\\x12=\\n\" +\n\t\"\\bresponse\\x18\\x03 \\x01(\\v2!.telepresence.manager.DNSResponseR\\bresponse\\\"+\\n\" +\n\t\"\\x05IPNet\\x12\\x0e\\n\" +\n\t\"\\x02ip\\x18\\x01 \\x01(\\fR\\x02ip\\x12\\x12\\n\" +\n\t\"\\x04mask\\x18\\x02 \\x01(\\x05R\\x04mask\\\"\\xf0\\x03\\n\" +\n\t\"\\vClusterInfo\\x12#\\n\" +\n\t\"\\rservice_cidrs\\x18\\x01 \\x03(\\fR\\fserviceCidrs\\x12B\\n\" +\n\t\"\\x0eservice_subnet\\x18\\x02 \\x01(\\v2\\x1b.telepresence.manager.IPNetR\\rserviceSubnet\\x12<\\n\" +\n\t\"\\vpod_subnets\\x18\\x03 \\x03(\\v2\\x1b.telepresence.manager.IPNetR\\n\" +\n\t\"podSubnets\\x12$\\n\" +\n\t\"\\x0emanager_pod_ip\\x18\\x05 \\x01(\\fR\\fmanagerPodIp\\x12(\\n\" +\n\t\"\\x10manager_pod_port\\x18\\b \\x01(\\x05R\\x0emanagerPodPort\\x12&\\n\" +\n\t\"\\x0finjector_svc_ip\\x18\\t \\x01(\\fR\\rinjectorSvcIp\\x12*\\n\" +\n\t\"\\x11injector_svc_port\\x18\\n\" +\n\t\" \\x01(\\x05R\\x0finjectorSvcPort\\x12*\\n\" +\n\t\"\\x11injector_svc_host\\x18\\v \\x01(\\tR\\x0finjectorSvcHost\\x127\\n\" +\n\t\"\\arouting\\x18\\x06 \\x01(\\v2\\x1d.telepresence.manager.RoutingR\\arouting\\x12+\\n\" +\n\t\"\\x03dns\\x18\\a \\x01(\\v2\\x19.telepresence.manager.DNSR\\x03dnsJ\\x04\\b\\x04\\x10\\x05\\\"\\xfa\\x01\\n\" +\n\t\"\\aRouting\\x12I\\n\" +\n\t\"\\x12also_proxy_subnets\\x18\\x01 \\x03(\\v2\\x1b.telepresence.manager.IPNetR\\x10alsoProxySubnets\\x12K\\n\" +\n\t\"\\x13never_proxy_subnets\\x18\\x02 \\x03(\\v2\\x1b.telepresence.manager.IPNetR\\x11neverProxySubnets\\x12W\\n\" +\n\t\"\\x19allow_conflicting_subnets\\x18\\x03 \\x03(\\v2\\x1b.telepresence.manager.IPNetR\\x17allowConflictingSubnets\\\"\\x9b\\x01\\n\" +\n\t\"\\x03DNS\\x12)\\n\" +\n\t\"\\x10include_suffixes\\x18\\x01 \\x03(\\tR\\x0fincludeSuffixes\\x12)\\n\" +\n\t\"\\x10exclude_suffixes\\x18\\x02 \\x03(\\tR\\x0fexcludeSuffixes\\x12\\x17\\n\" +\n\t\"\\akube_ip\\x18\\x03 \\x01(\\fR\\x06kubeIp\\x12%\\n\" +\n\t\"\\x0ecluster_domain\\x18\\x04 \\x01(\\tR\\rclusterDomain\\\",\\n\" +\n\t\"\\tCLIConfig\\x12\\x1f\\n\" +\n\t\"\\vconfig_yaml\\x18\\x01 \\x01(\\fR\\n\" +\n\t\"configYaml\\\"#\\n\" +\n\t\"\\rAgentImageFQN\\x12\\x12\\n\" +\n\t\"\\x05f_q_n\\x18\\x01 \\x01(\\tR\\x03fQN\\\"\\xd7\\x01\\n\" +\n\t\"\\fAgentPodInfo\\x12\\x15\\n\" +\n\t\"\\x06pod_id\\x18\\a \\x01(\\tR\\x05podId\\x12\\x19\\n\" +\n\t\"\\bpod_name\\x18\\x01 \\x01(\\tR\\apodName\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x02 \\x01(\\tR\\tnamespace\\x12\\x15\\n\" +\n\t\"\\x06pod_ip\\x18\\x03 \\x01(\\fR\\x05podIp\\x12\\x19\\n\" +\n\t\"\\bapi_port\\x18\\x04 \\x01(\\x05R\\aapiPort\\x12 \\n\" +\n\t\"\\vintercepted\\x18\\x05 \\x01(\\bR\\vintercepted\\x12#\\n\" +\n\t\"\\rworkload_name\\x18\\x06 \\x01(\\tR\\fworkloadName\\\"R\\n\" +\n\t\"\\x14AgentPodInfoSnapshot\\x12:\\n\" +\n\t\"\\x06agents\\x18\\x01 \\x03(\\v2\\\".telepresence.manager.AgentPodInfoR\\x06agents\\\"\\xdf\\x01\\n\" +\n\t\"\\x11AgentPodInfoDelta\\x12N\\n\" +\n\t\"\\aupserts\\x18\\x01 \\x03(\\v24.telepresence.manager.AgentPodInfoDelta.UpsertsEntryR\\aupserts\\x12\\x1a\\n\" +\n\t\"\\bremovals\\x18\\x02 \\x03(\\tR\\bremovals\\x1a^\\n\" +\n\t\"\\fUpsertsEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x128\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\v2\\\".telepresence.manager.AgentPodInfoR\\x05value:\\x028\\x01\\\"e\\n\" +\n\t\"\\x12AgentConfigRequest\\x12;\\n\" +\n\t\"\\asession\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\asession\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x02 \\x01(\\tR\\x04name\\\")\\n\" +\n\t\"\\x13AgentConfigResponse\\x12\\x12\\n\" +\n\t\"\\x04data\\x18\\x01 \\x01(\\fR\\x04data\\\"\\x83\\x01\\n\" +\n\t\"\\rTunnelMetrics\\x12*\\n\" +\n\t\"\\x11client_session_id\\x18\\x01 \\x01(\\tR\\x0fclientSessionId\\x12#\\n\" +\n\t\"\\ringress_bytes\\x18\\x02 \\x01(\\x04R\\fingressBytes\\x12!\\n\" +\n\t\"\\fegress_bytes\\x18\\x03 \\x01(\\x04R\\vegressBytes\\\"S\\n\" +\n\t\"\\x12KnownWorkloadKinds\\x12=\\n\" +\n\t\"\\x05kinds\\x18\\x01 \\x03(\\x0e2'.telepresence.manager.WorkloadInfo.KindR\\x05kinds\\\"\\x8d\\x05\\n\" +\n\t\"\\fWorkloadInfo\\x12;\\n\" +\n\t\"\\x04kind\\x18\\x01 \\x01(\\x0e2'.telepresence.manager.WorkloadInfo.KindR\\x04kind\\x12\\x12\\n\" +\n\t\"\\x04name\\x18\\x02 \\x01(\\tR\\x04name\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x03 \\x01(\\tR\\tnamespace\\x12\\x10\\n\" +\n\t\"\\x03uid\\x18\\a \\x01(\\tR\\x03uid\\x12N\\n\" +\n\t\"\\vagent_state\\x18\\x04 \\x01(\\x0e2-.telepresence.manager.WorkloadInfo.AgentStateR\\n\" +\n\t\"agentState\\x12Y\\n\" +\n\t\"\\x11intercept_clients\\x18\\x05 \\x03(\\v2,.telepresence.manager.WorkloadInfo.InterceptR\\x10interceptClients\\x12>\\n\" +\n\t\"\\x05state\\x18\\x06 \\x01(\\x0e2(.telepresence.manager.WorkloadInfo.StateR\\x05state\\x1a#\\n\" +\n\t\"\\tIntercept\\x12\\x16\\n\" +\n\t\"\\x06client\\x18\\x01 \\x01(\\tR\\x06client\\\"U\\n\" +\n\t\"\\x04Kind\\x12\\x0f\\n\" +\n\t\"\\vUNSPECIFIED\\x10\\x00\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"DEPLOYMENT\\x10\\x01\\x12\\x0e\\n\" +\n\t\"\\n\" +\n\t\"REPLICASET\\x10\\x02\\x12\\x0f\\n\" +\n\t\"\\vSTATEFULSET\\x10\\x03\\x12\\v\\n\" +\n\t\"\\aROLLOUT\\x10\\x04\\\"M\\n\" +\n\t\"\\x05State\\x12\\x17\\n\" +\n\t\"\\x13UNKNOWN_UNSPECIFIED\\x10\\x00\\x12\\r\\n\" +\n\t\"\\tAVAILABLE\\x10\\x01\\x12\\x0f\\n\" +\n\t\"\\vPROGRESSING\\x10\\x02\\x12\\v\\n\" +\n\t\"\\aFAILURE\\x10\\x03\\\"F\\n\" +\n\t\"\\n\" +\n\t\"AgentState\\x12\\x18\\n\" +\n\t\"\\x14NO_AGENT_UNSPECIFIED\\x10\\x00\\x12\\r\\n\" +\n\t\"\\tINSTALLED\\x10\\x01\\x12\\x0f\\n\" +\n\t\"\\vINTERCEPTED\\x10\\x02\\\"\\xc7\\x01\\n\" +\n\t\"\\rWorkloadEvent\\x12<\\n\" +\n\t\"\\x04type\\x18\\x01 \\x01(\\x0e2(.telepresence.manager.WorkloadEvent.TypeR\\x04type\\x12>\\n\" +\n\t\"\\bworkload\\x18\\x02 \\x01(\\v2\\\".telepresence.manager.WorkloadInfoR\\bworkload\\\"8\\n\" +\n\t\"\\x04Type\\x12\\x15\\n\" +\n\t\"\\x11ADDED_UNSPECIFIED\\x10\\x00\\x12\\f\\n\" +\n\t\"\\bMODIFIED\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aDELETED\\x10\\x02\\\"\\x84\\x01\\n\" +\n\t\"\\x13WorkloadEventsDelta\\x120\\n\" +\n\t\"\\x05since\\x18\\x01 \\x01(\\v2\\x1a.google.protobuf.TimestampR\\x05since\\x12;\\n\" +\n\t\"\\x06events\\x18\\x02 \\x03(\\v2#.telepresence.manager.WorkloadEventR\\x06events\\\"\\xad\\x01\\n\" +\n\t\"\\x15WorkloadEventsRequest\\x12D\\n\" +\n\t\"\\fsession_info\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\vsessionInfo\\x120\\n\" +\n\t\"\\x05since\\x18\\x02 \\x01(\\v2\\x1a.google.protobuf.TimestampR\\x05since\\x12\\x1c\\n\" +\n\t\"\\tnamespace\\x18\\x03 \\x01(\\tR\\tnamespace\\\"v\\n\" +\n\t\"\\x16UninstallAgentsRequest\\x12D\\n\" +\n\t\"\\fsession_info\\x18\\x01 \\x01(\\v2!.telepresence.manager.SessionInfoR\\vsessionInfo\\x12\\x16\\n\" +\n\t\"\\x06agents\\x18\\x02 \\x03(\\tR\\x06agents*\\xad\\x01\\n\" +\n\t\"\\x18InterceptDispositionType\\x12\\x0f\\n\" +\n\t\"\\vUNSPECIFIED\\x10\\x00\\x12\\n\" +\n\t\"\\n\" +\n\t\"\\x06ACTIVE\\x10\\x01\\x12\\v\\n\" +\n\t\"\\aWAITING\\x10\\x02\\x12\\v\\n\" +\n\t\"\\aREMOVED\\x10\\t\\x12\\r\\n\" +\n\t\"\\tNO_CLIENT\\x10\\x03\\x12\\f\\n\" +\n\t\"\\bNO_AGENT\\x10\\x04\\x12\\x10\\n\" +\n\t\"\\fNO_MECHANISM\\x10\\x05\\x12\\f\\n\" +\n\t\"\\bNO_PORTS\\x10\\x06\\x12\\x0f\\n\" +\n\t\"\\vAGENT_ERROR\\x10\\a\\x12\\f\\n\" +\n\t\"\\bBAD_ARGS\\x10\\b2\\xf4\\x17\\n\" +\n\t\"\\aManager\\x12E\\n\" +\n\t\"\\aVersion\\x12\\x16.google.protobuf.Empty\\x1a\\\".telepresence.manager.VersionInfo2\\x12O\\n\" +\n\t\"\\x10GetAgentImageFQN\\x12\\x16.google.protobuf.Empty\\x1a#.telepresence.manager.AgentImageFQN\\x12e\\n\" +\n\t\"\\x0eGetAgentConfig\\x12(.telepresence.manager.AgentConfigRequest\\x1a).telepresence.manager.AgentConfigResponse\\x12J\\n\" +\n\t\"\\x0fGetClientConfig\\x12\\x16.google.protobuf.Empty\\x1a\\x1f.telepresence.manager.CLIConfig\\x12W\\n\" +\n\t\"\\x12GetTelepresenceAPI\\x12\\x16.google.protobuf.Empty\\x1a).telepresence.manager.TelepresenceAPIInfo\\x12U\\n\" +\n\t\"\\x0eArriveAsClient\\x12 .telepresence.manager.ClientInfo\\x1a!.telepresence.manager.SessionInfo\\x12U\\n\" +\n\t\"\\x0eReconnectAgent\\x12+.telepresence.manager.ReconnectAgentRequest\\x1a\\x16.google.protobuf.Empty\\x12W\\n\" +\n\t\"\\x0fReconnectClient\\x12,.telepresence.manager.ReconnectClientRequest\\x1a\\x16.google.protobuf.Empty\\x12S\\n\" +\n\t\"\\rArriveAsAgent\\x12\\x1f.telepresence.manager.AgentInfo\\x1a!.telepresence.manager.SessionInfo\\x12E\\n\" +\n\t\"\\x06Remain\\x12#.telepresence.manager.RemainRequest\\x1a\\x16.google.protobuf.Empty\\x12C\\n\" +\n\t\"\\x06Depart\\x12!.telepresence.manager.SessionInfo\\x1a\\x16.google.protobuf.Empty\\x12L\\n\" +\n\t\"\\vSetLogLevel\\x12%.telepresence.manager.LogLevelRequest\\x1a\\x16.google.protobuf.Empty\\x12S\\n\" +\n\t\"\\aGetLogs\\x12$.telepresence.manager.GetLogsRequest\\x1a\\\".telepresence.manager.LogsResponse\\x12a\\n\" +\n\t\"\\x0eWatchAgentPods\\x12!.telepresence.manager.SessionInfo\\x1a*.telepresence.manager.AgentPodInfoSnapshot0\\x01\\x12c\\n\" +\n\t\"\\x13WatchAgentPodsDelta\\x12!.telepresence.manager.SessionInfo\\x1a'.telepresence.manager.AgentPodInfoDelta0\\x01\\x12[\\n\" +\n\t\"\\vWatchAgents\\x12!.telepresence.manager.SessionInfo\\x1a'.telepresence.manager.AgentInfoSnapshot0\\x01\\x12]\\n\" +\n\t\"\\x10WatchAgentsDelta\\x12!.telepresence.manager.SessionInfo\\x1a$.telepresence.manager.AgentInfoDelta0\\x01\\x12c\\n\" +\n\t\"\\x0fWatchIntercepts\\x12!.telepresence.manager.SessionInfo\\x1a+.telepresence.manager.InterceptInfoSnapshot0\\x01\\x12e\\n\" +\n\t\"\\x14WatchInterceptsDelta\\x12!.telepresence.manager.SessionInfo\\x1a(.telepresence.manager.InterceptInfoDelta0\\x01\\x12j\\n\" +\n\t\"\\x0eWatchWorkloads\\x12+.telepresence.manager.WorkloadEventsRequest\\x1a).telepresence.manager.WorkloadEventsDelta0\\x01\\x12Z\\n\" +\n\t\"\\x10WatchClusterInfo\\x12!.telepresence.manager.SessionInfo\\x1a!.telepresence.manager.ClusterInfo0\\x01\\x12`\\n\" +\n\t\"\\vEnsureAgent\\x12(.telepresence.manager.EnsureAgentRequest\\x1a'.telepresence.manager.AgentInfoSnapshot\\x12i\\n\" +\n\t\"\\x10PrepareIntercept\\x12,.telepresence.manager.CreateInterceptRequest\\x1a'.telepresence.manager.PreparedIntercept\\x12d\\n\" +\n\t\"\\x0fCreateIntercept\\x12,.telepresence.manager.CreateInterceptRequest\\x1a#.telepresence.manager.InterceptInfo\\x12X\\n\" +\n\t\"\\x0fRemoveIntercept\\x12-.telepresence.manager.RemoveInterceptRequest2\\x1a\\x16.google.protobuf.Empty\\x12^\\n\" +\n\t\"\\fGetIntercept\\x12).telepresence.manager.GetInterceptRequest\\x1a#.telepresence.manager.InterceptInfo\\x12W\\n\" +\n\t\"\\x0fReviewIntercept\\x12,.telepresence.manager.ReviewInterceptRequest\\x1a\\x16.google.protobuf.Empty\\x12d\\n\" +\n\t\"\\x15GetKnownWorkloadKinds\\x12!.telepresence.manager.SessionInfo\\x1a(.telepresence.manager.KnownWorkloadKinds\\x12S\\n\" +\n\t\"\\x06Lookup\\x12#.telepresence.manager.LookupRequest\\x1a$.telepresence.manager.LookupResponse\\x12P\\n\" +\n\t\"\\tLookupDNS\\x12 .telepresence.manager.DNSRequest\\x1a!.telepresence.manager.DNSResponse\\x12P\\n\" +\n\t\"\\rWatchLogLevel\\x12\\x16.google.protobuf.Empty\\x1a%.telepresence.manager.LogLevelRequest0\\x01\\x12V\\n\" +\n\t\"\\x06Tunnel\\x12#.telepresence.manager.TunnelMessage\\x1a#.telepresence.manager.TunnelMessage(\\x010\\x01\\x12L\\n\" +\n\t\"\\rReportMetrics\\x12#.telepresence.manager.TunnelMetrics\\x1a\\x16.google.protobuf.Empty\\x12W\\n\" +\n\t\"\\x0fUninstallAgents\\x12,.telepresence.manager.UninstallAgentsRequest\\x1a\\x16.google.protobuf.EmptyB7Z5github.com/telepresenceio/telepresence/rpc/v2/managerb\\x06proto3\"\n\nvar (\n\tfile_manager_manager_proto_rawDescOnce sync.Once\n\tfile_manager_manager_proto_rawDescData []byte\n)\n\nfunc file_manager_manager_proto_rawDescGZIP() []byte {\n\tfile_manager_manager_proto_rawDescOnce.Do(func() {\n\t\tfile_manager_manager_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_manager_manager_proto_rawDesc), len(file_manager_manager_proto_rawDesc)))\n\t})\n\treturn file_manager_manager_proto_rawDescData\n}\n\nvar file_manager_manager_proto_enumTypes = make([]protoimpl.EnumInfo, 5)\nvar file_manager_manager_proto_msgTypes = make([]protoimpl.MessageInfo, 67)\nvar file_manager_manager_proto_goTypes = []any{\n\t(InterceptDispositionType)(0),   // 0: telepresence.manager.InterceptDispositionType\n\t(WorkloadInfo_Kind)(0),          // 1: telepresence.manager.WorkloadInfo.Kind\n\t(WorkloadInfo_State)(0),         // 2: telepresence.manager.WorkloadInfo.State\n\t(WorkloadInfo_AgentState)(0),    // 3: telepresence.manager.WorkloadInfo.AgentState\n\t(WorkloadEvent_Type)(0),         // 4: telepresence.manager.WorkloadEvent.Type\n\t(*ClientInfo)(nil),              // 5: telepresence.manager.ClientInfo\n\t(*AgentInfo)(nil),               // 6: telepresence.manager.AgentInfo\n\t(*PortMapping)(nil),             // 7: telepresence.manager.PortMapping\n\t(*InterceptSpec)(nil),           // 8: telepresence.manager.InterceptSpec\n\t(*InterceptInfo)(nil),           // 9: telepresence.manager.InterceptInfo\n\t(*ReconnectAgentRequest)(nil),   // 10: telepresence.manager.ReconnectAgentRequest\n\t(*ReconnectClientRequest)(nil),  // 11: telepresence.manager.ReconnectClientRequest\n\t(*SessionInfo)(nil),             // 12: telepresence.manager.SessionInfo\n\t(*AgentsRequest)(nil),           // 13: telepresence.manager.AgentsRequest\n\t(*AgentInfoSnapshot)(nil),       // 14: telepresence.manager.AgentInfoSnapshot\n\t(*AgentInfoDelta)(nil),          // 15: telepresence.manager.AgentInfoDelta\n\t(*InterceptInfoSnapshot)(nil),   // 16: telepresence.manager.InterceptInfoSnapshot\n\t(*InterceptInfoDelta)(nil),      // 17: telepresence.manager.InterceptInfoDelta\n\t(*CreateInterceptRequest)(nil),  // 18: telepresence.manager.CreateInterceptRequest\n\t(*EnsureAgentRequest)(nil),      // 19: telepresence.manager.EnsureAgentRequest\n\t(*PreparedIntercept)(nil),       // 20: telepresence.manager.PreparedIntercept\n\t(*RemoveInterceptRequest2)(nil), // 21: telepresence.manager.RemoveInterceptRequest2\n\t(*GetInterceptRequest)(nil),     // 22: telepresence.manager.GetInterceptRequest\n\t(*ReviewInterceptRequest)(nil),  // 23: telepresence.manager.ReviewInterceptRequest\n\t(*RemainRequest)(nil),           // 24: telepresence.manager.RemainRequest\n\t(*LogLevelRequest)(nil),         // 25: telepresence.manager.LogLevelRequest\n\t(*GetLogsRequest)(nil),          // 26: telepresence.manager.GetLogsRequest\n\t(*LogsResponse)(nil),            // 27: telepresence.manager.LogsResponse\n\t(*TelepresenceAPIInfo)(nil),     // 28: telepresence.manager.TelepresenceAPIInfo\n\t(*VersionInfo2)(nil),            // 29: telepresence.manager.VersionInfo2\n\t(*TunnelMessage)(nil),           // 30: telepresence.manager.TunnelMessage\n\t(*DialRequest)(nil),             // 31: telepresence.manager.DialRequest\n\t(*LookupRequest)(nil),           // 32: telepresence.manager.LookupRequest\n\t(*LookupResponse)(nil),          // 33: telepresence.manager.LookupResponse\n\t(*DNSRequest)(nil),              // 34: telepresence.manager.DNSRequest\n\t(*DNSResponse)(nil),             // 35: telepresence.manager.DNSResponse\n\t(*DNSAgentResponse)(nil),        // 36: telepresence.manager.DNSAgentResponse\n\t(*IPNet)(nil),                   // 37: telepresence.manager.IPNet\n\t(*ClusterInfo)(nil),             // 38: telepresence.manager.ClusterInfo\n\t(*Routing)(nil),                 // 39: telepresence.manager.Routing\n\t(*DNS)(nil),                     // 40: telepresence.manager.DNS\n\t(*CLIConfig)(nil),               // 41: telepresence.manager.CLIConfig\n\t(*AgentImageFQN)(nil),           // 42: telepresence.manager.AgentImageFQN\n\t(*AgentPodInfo)(nil),            // 43: telepresence.manager.AgentPodInfo\n\t(*AgentPodInfoSnapshot)(nil),    // 44: telepresence.manager.AgentPodInfoSnapshot\n\t(*AgentPodInfoDelta)(nil),       // 45: telepresence.manager.AgentPodInfoDelta\n\t(*AgentConfigRequest)(nil),      // 46: telepresence.manager.AgentConfigRequest\n\t(*AgentConfigResponse)(nil),     // 47: telepresence.manager.AgentConfigResponse\n\t(*TunnelMetrics)(nil),           // 48: telepresence.manager.TunnelMetrics\n\t(*KnownWorkloadKinds)(nil),      // 49: telepresence.manager.KnownWorkloadKinds\n\t(*WorkloadInfo)(nil),            // 50: telepresence.manager.WorkloadInfo\n\t(*WorkloadEvent)(nil),           // 51: telepresence.manager.WorkloadEvent\n\t(*WorkloadEventsDelta)(nil),     // 52: telepresence.manager.WorkloadEventsDelta\n\t(*WorkloadEventsRequest)(nil),   // 53: telepresence.manager.WorkloadEventsRequest\n\t(*UninstallAgentsRequest)(nil),  // 54: telepresence.manager.UninstallAgentsRequest\n\t(*AgentInfo_Mechanism)(nil),     // 55: telepresence.manager.AgentInfo.Mechanism\n\t(*AgentInfo_ContainerInfo)(nil), // 56: telepresence.manager.AgentInfo.ContainerInfo\n\tnil,                             // 57: telepresence.manager.AgentInfo.ContainersEntry\n\tnil,                             // 58: telepresence.manager.AgentInfo.ContainerInfo.EnvironmentEntry\n\tnil,                             // 59: telepresence.manager.AgentInfo.ContainerInfo.MountsEntry\n\tnil,                             // 60: telepresence.manager.InterceptSpec.HeaderFiltersEntry\n\tnil,                             // 61: telepresence.manager.InterceptSpec.MetadataEntry\n\tnil,                             // 62: telepresence.manager.InterceptInfo.EnvironmentEntry\n\tnil,                             // 63: telepresence.manager.InterceptInfo.MountsEntry\n\tnil,                             // 64: telepresence.manager.AgentInfoDelta.UpsertsEntry\n\tnil,                             // 65: telepresence.manager.InterceptInfoDelta.UpsertsEntry\n\tnil,                             // 66: telepresence.manager.ReviewInterceptRequest.EnvironmentEntry\n\tnil,                             // 67: telepresence.manager.ReviewInterceptRequest.MountsEntry\n\tnil,                             // 68: telepresence.manager.LogsResponse.PodLogsEntry\n\tnil,                             // 69: telepresence.manager.LogsResponse.PodYamlEntry\n\tnil,                             // 70: telepresence.manager.AgentPodInfoDelta.UpsertsEntry\n\t(*WorkloadInfo_Intercept)(nil),  // 71: telepresence.manager.WorkloadInfo.Intercept\n\t(*timestamppb.Timestamp)(nil),   // 72: google.protobuf.Timestamp\n\t(*durationpb.Duration)(nil),     // 73: google.protobuf.Duration\n\t(*emptypb.Empty)(nil),           // 74: google.protobuf.Empty\n}\nvar file_manager_manager_proto_depIdxs = []int32{\n\t55,  // 0: telepresence.manager.AgentInfo.mechanisms:type_name -> telepresence.manager.AgentInfo.Mechanism\n\t57,  // 1: telepresence.manager.AgentInfo.containers:type_name -> telepresence.manager.AgentInfo.ContainersEntry\n\t60,  // 2: telepresence.manager.InterceptSpec.header_filters:type_name -> telepresence.manager.InterceptSpec.HeaderFiltersEntry\n\t61,  // 3: telepresence.manager.InterceptSpec.metadata:type_name -> telepresence.manager.InterceptSpec.MetadataEntry\n\t8,   // 4: telepresence.manager.InterceptInfo.spec:type_name -> telepresence.manager.InterceptSpec\n\t12,  // 5: telepresence.manager.InterceptInfo.client_session:type_name -> telepresence.manager.SessionInfo\n\t0,   // 6: telepresence.manager.InterceptInfo.disposition:type_name -> telepresence.manager.InterceptDispositionType\n\t62,  // 7: telepresence.manager.InterceptInfo.environment:type_name -> telepresence.manager.InterceptInfo.EnvironmentEntry\n\t63,  // 8: telepresence.manager.InterceptInfo.mounts:type_name -> telepresence.manager.InterceptInfo.MountsEntry\n\t72,  // 9: telepresence.manager.InterceptInfo.modified_at:type_name -> google.protobuf.Timestamp\n\t12,  // 10: telepresence.manager.ReconnectAgentRequest.session:type_name -> telepresence.manager.SessionInfo\n\t6,   // 11: telepresence.manager.ReconnectAgentRequest.agent:type_name -> telepresence.manager.AgentInfo\n\t12,  // 12: telepresence.manager.ReconnectClientRequest.session:type_name -> telepresence.manager.SessionInfo\n\t5,   // 13: telepresence.manager.ReconnectClientRequest.client:type_name -> telepresence.manager.ClientInfo\n\t9,   // 14: telepresence.manager.ReconnectClientRequest.intercepts:type_name -> telepresence.manager.InterceptInfo\n\t6,   // 15: telepresence.manager.ReconnectClientRequest.agents:type_name -> telepresence.manager.AgentInfo\n\t12,  // 16: telepresence.manager.AgentsRequest.session:type_name -> telepresence.manager.SessionInfo\n\t6,   // 17: telepresence.manager.AgentInfoSnapshot.agents:type_name -> telepresence.manager.AgentInfo\n\t64,  // 18: telepresence.manager.AgentInfoDelta.upserts:type_name -> telepresence.manager.AgentInfoDelta.UpsertsEntry\n\t9,   // 19: telepresence.manager.InterceptInfoSnapshot.intercepts:type_name -> telepresence.manager.InterceptInfo\n\t65,  // 20: telepresence.manager.InterceptInfoDelta.upserts:type_name -> telepresence.manager.InterceptInfoDelta.UpsertsEntry\n\t12,  // 21: telepresence.manager.CreateInterceptRequest.session:type_name -> telepresence.manager.SessionInfo\n\t8,   // 22: telepresence.manager.CreateInterceptRequest.intercept_spec:type_name -> telepresence.manager.InterceptSpec\n\t12,  // 23: telepresence.manager.EnsureAgentRequest.session:type_name -> telepresence.manager.SessionInfo\n\t12,  // 24: telepresence.manager.RemoveInterceptRequest2.session:type_name -> telepresence.manager.SessionInfo\n\t12,  // 25: telepresence.manager.GetInterceptRequest.session:type_name -> telepresence.manager.SessionInfo\n\t12,  // 26: telepresence.manager.ReviewInterceptRequest.session:type_name -> telepresence.manager.SessionInfo\n\t0,   // 27: telepresence.manager.ReviewInterceptRequest.disposition:type_name -> telepresence.manager.InterceptDispositionType\n\t66,  // 28: telepresence.manager.ReviewInterceptRequest.environment:type_name -> telepresence.manager.ReviewInterceptRequest.EnvironmentEntry\n\t67,  // 29: telepresence.manager.ReviewInterceptRequest.mounts:type_name -> telepresence.manager.ReviewInterceptRequest.MountsEntry\n\t12,  // 30: telepresence.manager.RemainRequest.session:type_name -> telepresence.manager.SessionInfo\n\t72,  // 31: telepresence.manager.RemainRequest.last_activity:type_name -> google.protobuf.Timestamp\n\t73,  // 32: telepresence.manager.LogLevelRequest.duration:type_name -> google.protobuf.Duration\n\t68,  // 33: telepresence.manager.LogsResponse.pod_logs:type_name -> telepresence.manager.LogsResponse.PodLogsEntry\n\t69,  // 34: telepresence.manager.LogsResponse.pod_yaml:type_name -> telepresence.manager.LogsResponse.PodYamlEntry\n\t12,  // 35: telepresence.manager.LookupRequest.session:type_name -> telepresence.manager.SessionInfo\n\t12,  // 36: telepresence.manager.DNSRequest.session:type_name -> telepresence.manager.SessionInfo\n\t12,  // 37: telepresence.manager.DNSAgentResponse.session:type_name -> telepresence.manager.SessionInfo\n\t34,  // 38: telepresence.manager.DNSAgentResponse.request:type_name -> telepresence.manager.DNSRequest\n\t35,  // 39: telepresence.manager.DNSAgentResponse.response:type_name -> telepresence.manager.DNSResponse\n\t37,  // 40: telepresence.manager.ClusterInfo.service_subnet:type_name -> telepresence.manager.IPNet\n\t37,  // 41: telepresence.manager.ClusterInfo.pod_subnets:type_name -> telepresence.manager.IPNet\n\t39,  // 42: telepresence.manager.ClusterInfo.routing:type_name -> telepresence.manager.Routing\n\t40,  // 43: telepresence.manager.ClusterInfo.dns:type_name -> telepresence.manager.DNS\n\t37,  // 44: telepresence.manager.Routing.also_proxy_subnets:type_name -> telepresence.manager.IPNet\n\t37,  // 45: telepresence.manager.Routing.never_proxy_subnets:type_name -> telepresence.manager.IPNet\n\t37,  // 46: telepresence.manager.Routing.allow_conflicting_subnets:type_name -> telepresence.manager.IPNet\n\t43,  // 47: telepresence.manager.AgentPodInfoSnapshot.agents:type_name -> telepresence.manager.AgentPodInfo\n\t70,  // 48: telepresence.manager.AgentPodInfoDelta.upserts:type_name -> telepresence.manager.AgentPodInfoDelta.UpsertsEntry\n\t12,  // 49: telepresence.manager.AgentConfigRequest.session:type_name -> telepresence.manager.SessionInfo\n\t1,   // 50: telepresence.manager.KnownWorkloadKinds.kinds:type_name -> telepresence.manager.WorkloadInfo.Kind\n\t1,   // 51: telepresence.manager.WorkloadInfo.kind:type_name -> telepresence.manager.WorkloadInfo.Kind\n\t3,   // 52: telepresence.manager.WorkloadInfo.agent_state:type_name -> telepresence.manager.WorkloadInfo.AgentState\n\t71,  // 53: telepresence.manager.WorkloadInfo.intercept_clients:type_name -> telepresence.manager.WorkloadInfo.Intercept\n\t2,   // 54: telepresence.manager.WorkloadInfo.state:type_name -> telepresence.manager.WorkloadInfo.State\n\t4,   // 55: telepresence.manager.WorkloadEvent.type:type_name -> telepresence.manager.WorkloadEvent.Type\n\t50,  // 56: telepresence.manager.WorkloadEvent.workload:type_name -> telepresence.manager.WorkloadInfo\n\t72,  // 57: telepresence.manager.WorkloadEventsDelta.since:type_name -> google.protobuf.Timestamp\n\t51,  // 58: telepresence.manager.WorkloadEventsDelta.events:type_name -> telepresence.manager.WorkloadEvent\n\t12,  // 59: telepresence.manager.WorkloadEventsRequest.session_info:type_name -> telepresence.manager.SessionInfo\n\t72,  // 60: telepresence.manager.WorkloadEventsRequest.since:type_name -> google.protobuf.Timestamp\n\t12,  // 61: telepresence.manager.UninstallAgentsRequest.session_info:type_name -> telepresence.manager.SessionInfo\n\t58,  // 62: telepresence.manager.AgentInfo.ContainerInfo.environment:type_name -> telepresence.manager.AgentInfo.ContainerInfo.EnvironmentEntry\n\t59,  // 63: telepresence.manager.AgentInfo.ContainerInfo.mounts:type_name -> telepresence.manager.AgentInfo.ContainerInfo.MountsEntry\n\t56,  // 64: telepresence.manager.AgentInfo.ContainersEntry.value:type_name -> telepresence.manager.AgentInfo.ContainerInfo\n\t6,   // 65: telepresence.manager.AgentInfoDelta.UpsertsEntry.value:type_name -> telepresence.manager.AgentInfo\n\t9,   // 66: telepresence.manager.InterceptInfoDelta.UpsertsEntry.value:type_name -> telepresence.manager.InterceptInfo\n\t43,  // 67: telepresence.manager.AgentPodInfoDelta.UpsertsEntry.value:type_name -> telepresence.manager.AgentPodInfo\n\t74,  // 68: telepresence.manager.Manager.Version:input_type -> google.protobuf.Empty\n\t74,  // 69: telepresence.manager.Manager.GetAgentImageFQN:input_type -> google.protobuf.Empty\n\t46,  // 70: telepresence.manager.Manager.GetAgentConfig:input_type -> telepresence.manager.AgentConfigRequest\n\t74,  // 71: telepresence.manager.Manager.GetClientConfig:input_type -> google.protobuf.Empty\n\t74,  // 72: telepresence.manager.Manager.GetTelepresenceAPI:input_type -> google.protobuf.Empty\n\t5,   // 73: telepresence.manager.Manager.ArriveAsClient:input_type -> telepresence.manager.ClientInfo\n\t10,  // 74: telepresence.manager.Manager.ReconnectAgent:input_type -> telepresence.manager.ReconnectAgentRequest\n\t11,  // 75: telepresence.manager.Manager.ReconnectClient:input_type -> telepresence.manager.ReconnectClientRequest\n\t6,   // 76: telepresence.manager.Manager.ArriveAsAgent:input_type -> telepresence.manager.AgentInfo\n\t24,  // 77: telepresence.manager.Manager.Remain:input_type -> telepresence.manager.RemainRequest\n\t12,  // 78: telepresence.manager.Manager.Depart:input_type -> telepresence.manager.SessionInfo\n\t25,  // 79: telepresence.manager.Manager.SetLogLevel:input_type -> telepresence.manager.LogLevelRequest\n\t26,  // 80: telepresence.manager.Manager.GetLogs:input_type -> telepresence.manager.GetLogsRequest\n\t12,  // 81: telepresence.manager.Manager.WatchAgentPods:input_type -> telepresence.manager.SessionInfo\n\t12,  // 82: telepresence.manager.Manager.WatchAgentPodsDelta:input_type -> telepresence.manager.SessionInfo\n\t12,  // 83: telepresence.manager.Manager.WatchAgents:input_type -> telepresence.manager.SessionInfo\n\t12,  // 84: telepresence.manager.Manager.WatchAgentsDelta:input_type -> telepresence.manager.SessionInfo\n\t12,  // 85: telepresence.manager.Manager.WatchIntercepts:input_type -> telepresence.manager.SessionInfo\n\t12,  // 86: telepresence.manager.Manager.WatchInterceptsDelta:input_type -> telepresence.manager.SessionInfo\n\t53,  // 87: telepresence.manager.Manager.WatchWorkloads:input_type -> telepresence.manager.WorkloadEventsRequest\n\t12,  // 88: telepresence.manager.Manager.WatchClusterInfo:input_type -> telepresence.manager.SessionInfo\n\t19,  // 89: telepresence.manager.Manager.EnsureAgent:input_type -> telepresence.manager.EnsureAgentRequest\n\t18,  // 90: telepresence.manager.Manager.PrepareIntercept:input_type -> telepresence.manager.CreateInterceptRequest\n\t18,  // 91: telepresence.manager.Manager.CreateIntercept:input_type -> telepresence.manager.CreateInterceptRequest\n\t21,  // 92: telepresence.manager.Manager.RemoveIntercept:input_type -> telepresence.manager.RemoveInterceptRequest2\n\t22,  // 93: telepresence.manager.Manager.GetIntercept:input_type -> telepresence.manager.GetInterceptRequest\n\t23,  // 94: telepresence.manager.Manager.ReviewIntercept:input_type -> telepresence.manager.ReviewInterceptRequest\n\t12,  // 95: telepresence.manager.Manager.GetKnownWorkloadKinds:input_type -> telepresence.manager.SessionInfo\n\t32,  // 96: telepresence.manager.Manager.Lookup:input_type -> telepresence.manager.LookupRequest\n\t34,  // 97: telepresence.manager.Manager.LookupDNS:input_type -> telepresence.manager.DNSRequest\n\t74,  // 98: telepresence.manager.Manager.WatchLogLevel:input_type -> google.protobuf.Empty\n\t30,  // 99: telepresence.manager.Manager.Tunnel:input_type -> telepresence.manager.TunnelMessage\n\t48,  // 100: telepresence.manager.Manager.ReportMetrics:input_type -> telepresence.manager.TunnelMetrics\n\t54,  // 101: telepresence.manager.Manager.UninstallAgents:input_type -> telepresence.manager.UninstallAgentsRequest\n\t29,  // 102: telepresence.manager.Manager.Version:output_type -> telepresence.manager.VersionInfo2\n\t42,  // 103: telepresence.manager.Manager.GetAgentImageFQN:output_type -> telepresence.manager.AgentImageFQN\n\t47,  // 104: telepresence.manager.Manager.GetAgentConfig:output_type -> telepresence.manager.AgentConfigResponse\n\t41,  // 105: telepresence.manager.Manager.GetClientConfig:output_type -> telepresence.manager.CLIConfig\n\t28,  // 106: telepresence.manager.Manager.GetTelepresenceAPI:output_type -> telepresence.manager.TelepresenceAPIInfo\n\t12,  // 107: telepresence.manager.Manager.ArriveAsClient:output_type -> telepresence.manager.SessionInfo\n\t74,  // 108: telepresence.manager.Manager.ReconnectAgent:output_type -> google.protobuf.Empty\n\t74,  // 109: telepresence.manager.Manager.ReconnectClient:output_type -> google.protobuf.Empty\n\t12,  // 110: telepresence.manager.Manager.ArriveAsAgent:output_type -> telepresence.manager.SessionInfo\n\t74,  // 111: telepresence.manager.Manager.Remain:output_type -> google.protobuf.Empty\n\t74,  // 112: telepresence.manager.Manager.Depart:output_type -> google.protobuf.Empty\n\t74,  // 113: telepresence.manager.Manager.SetLogLevel:output_type -> google.protobuf.Empty\n\t27,  // 114: telepresence.manager.Manager.GetLogs:output_type -> telepresence.manager.LogsResponse\n\t44,  // 115: telepresence.manager.Manager.WatchAgentPods:output_type -> telepresence.manager.AgentPodInfoSnapshot\n\t45,  // 116: telepresence.manager.Manager.WatchAgentPodsDelta:output_type -> telepresence.manager.AgentPodInfoDelta\n\t14,  // 117: telepresence.manager.Manager.WatchAgents:output_type -> telepresence.manager.AgentInfoSnapshot\n\t15,  // 118: telepresence.manager.Manager.WatchAgentsDelta:output_type -> telepresence.manager.AgentInfoDelta\n\t16,  // 119: telepresence.manager.Manager.WatchIntercepts:output_type -> telepresence.manager.InterceptInfoSnapshot\n\t17,  // 120: telepresence.manager.Manager.WatchInterceptsDelta:output_type -> telepresence.manager.InterceptInfoDelta\n\t52,  // 121: telepresence.manager.Manager.WatchWorkloads:output_type -> telepresence.manager.WorkloadEventsDelta\n\t38,  // 122: telepresence.manager.Manager.WatchClusterInfo:output_type -> telepresence.manager.ClusterInfo\n\t14,  // 123: telepresence.manager.Manager.EnsureAgent:output_type -> telepresence.manager.AgentInfoSnapshot\n\t20,  // 124: telepresence.manager.Manager.PrepareIntercept:output_type -> telepresence.manager.PreparedIntercept\n\t9,   // 125: telepresence.manager.Manager.CreateIntercept:output_type -> telepresence.manager.InterceptInfo\n\t74,  // 126: telepresence.manager.Manager.RemoveIntercept:output_type -> google.protobuf.Empty\n\t9,   // 127: telepresence.manager.Manager.GetIntercept:output_type -> telepresence.manager.InterceptInfo\n\t74,  // 128: telepresence.manager.Manager.ReviewIntercept:output_type -> google.protobuf.Empty\n\t49,  // 129: telepresence.manager.Manager.GetKnownWorkloadKinds:output_type -> telepresence.manager.KnownWorkloadKinds\n\t33,  // 130: telepresence.manager.Manager.Lookup:output_type -> telepresence.manager.LookupResponse\n\t35,  // 131: telepresence.manager.Manager.LookupDNS:output_type -> telepresence.manager.DNSResponse\n\t25,  // 132: telepresence.manager.Manager.WatchLogLevel:output_type -> telepresence.manager.LogLevelRequest\n\t30,  // 133: telepresence.manager.Manager.Tunnel:output_type -> telepresence.manager.TunnelMessage\n\t74,  // 134: telepresence.manager.Manager.ReportMetrics:output_type -> google.protobuf.Empty\n\t74,  // 135: telepresence.manager.Manager.UninstallAgents:output_type -> google.protobuf.Empty\n\t102, // [102:136] is the sub-list for method output_type\n\t68,  // [68:102] is the sub-list for method input_type\n\t68,  // [68:68] is the sub-list for extension type_name\n\t68,  // [68:68] is the sub-list for extension extendee\n\t0,   // [0:68] is the sub-list for field type_name\n}\n\nfunc init() { file_manager_manager_proto_init() }\nfunc file_manager_manager_proto_init() {\n\tif File_manager_manager_proto != nil {\n\t\treturn\n\t}\n\tfile_manager_manager_proto_msgTypes[7].OneofWrappers = []any{}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_manager_manager_proto_rawDesc), len(file_manager_manager_proto_rawDesc)),\n\t\t\tNumEnums:      5,\n\t\t\tNumMessages:   67,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_manager_manager_proto_goTypes,\n\t\tDependencyIndexes: file_manager_manager_proto_depIdxs,\n\t\tEnumInfos:         file_manager_manager_proto_enumTypes,\n\t\tMessageInfos:      file_manager_manager_proto_msgTypes,\n\t}.Build()\n\tFile_manager_manager_proto = out.File\n\tfile_manager_manager_proto_goTypes = nil\n\tfile_manager_manager_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "rpc/manager/manager.proto",
    "content": "syntax = \"proto3\";\n\n// The \"manager\" package describes the server implemented by the\n// in-cluster Manager, which is spoken to by the Agent (app-sidecar),\n// the on-laptop Connector (user-daemon), and the on-laptop CLI.\npackage telepresence.manager;\n\nimport \"google/protobuf/duration.proto\";\nimport \"google/protobuf/empty.proto\";\nimport \"google/protobuf/timestamp.proto\";\n\noption go_package = \"github.com/telepresenceio/telepresence/rpc/v2/manager\";\n\n// ClientInfo is the self-reported metadata that the on-laptop\n// Telepresence client reports whenever it connects to the in-cluster\n// Manager.\nmessage ClientInfo {\n  string name = 1;  // user@hostname\n  string namespace = 6; // namespace that the client is connected to\n  string install_id = 2;\n  string product = 3;  // \"telepresence\"\n  string version = 4;\n\n  reserved 5; // was api_key\n}\n\n// AgentInfo is the self-reported metadata that an Agent (app-sidecar)\n// reports at boot-up when it connects to the Telepresence Manager.\nmessage AgentInfo {\n  string name = 1;      // name of the Workload\n  string kind = 13;\n  string namespace = 7; // namespace of the Workload\n  string pod_name = 8;  // Pod name (from metadata.name)\n  string pod_ip = 2;    // Pod IP (from status.podIP)\n  string pod_uid = 14;  // Pod UID\n  int32 api_port = 9;   // Port number for the agent gRPC API\n  int32 sftp_port = 10; // Port number for the agent SFTP server\n  int32 ftp_port = 11;  // Port number for the agent FTP server\n  string product = 3;   // distinguish open source, our closed source, someone else's thing\n  string version = 4;\n\n  // \"Mechanisms\" are the ways that an Agent can decide handle\n  // incoming requests, and decide whether to send them to the\n  // in-cluster service, or whether to intercept them.  The \"tcp\"\n  // mechanism is the only one in Telepresence open source, and\n  // handles things at the TCP-level and either intercepts all TCP\n  // streams or doesn't intercept anything.  Other Agents than the\n  // Telepresence one may implement more mechanisms, such as\n  // Ambassador Labs' \"Service Preview\" Agent which implements the\n  // \"http\" mechanism which handles th \"http\" mechanism, which handles\n  // things at the HTTP-request-level and can decide to intercept\n  // individual HTTP requests based on the request headers.\n  message Mechanism {\n    string name = 1; // \"tcp\" or \"http\" or \"grpc\" or ...\n    string product = 2; // distinguish open source, our closed source, someone else's thing\n    string version = 3;\n  }\n\n  // This is a list of the mechanisms that the Agent advertises that\n  // it supports.\n  repeated Mechanism mechanisms = 5;\n\n  message ContainerInfo {\n    // The container environment\n    map<string, string> environment = 1;\n\n    // The directory where the intercept mounts can be found in the agent\n    string mount_point = 4;\n\n    // Map of mount -> MountPolicy\n    map<string, int32> mounts = 13;\n  }\n  map<string,ContainerInfo> containers = 12;\n\n  reserved 6;\n}\n\n// PortMapping describes a mapping from a port number in the intercepted container to\n// a port on the client for --from-pod and or vice versa when using --to-pod.\nmessage PortMapping {\n  int32 from = 1;\n  int32 to = 2;\n}\n\n// InterceptSpec contains static information about an intercept. It is shared by\n// all running agent instances.\nmessage InterceptSpec {\n  // A human-friendly name for this intercept.  This is usually the\n  // same as the agent name below; the name/namespace of the\n  // Workload, but it could be something else.  It is invalid for\n  // the same client to attempt to create multiple intercepts with the\n  // same name.\n  string name = 1;\n\n  // Same as ClientInfo.Name; \"user@hostname\".\n  string client = 2;\n\n  // Same as AgentInfo.Name of the Workload.\n  string agent = 3;\n\n  // Kind of the Workload\n  string workload_kind = 13;\n\n  // Same as AgentInfo.Namespace of the Workload\n  string namespace = 8;\n\n  // How to decide which subset of requests to that agent to intercept.\n  string mechanism = 4;\n\n  // The host that the target_ports are routed to.\n  string target_host = 6;\n\n  // Ports that will be forwarded from the intercepting pod's IP address\n  // to the target_host, using the following syntax:\n  //\n  // PORT = port-decl [\"/\" protocol ]\n  // port-decl = port-spec [ \":\" uint16 ]\n  // protocol = \"TCP\" | \"UDP\"\n  // port-spec = name | uint16\n  //\n  // If two numbers are used, they signify source:destination.\n  repeated string pod_ports = 5;\n\n  // Ports that will be forwarded from the intercepting client's localhost\n  // to the intercepted pod. Uses the same syntax as target_ports.\n  repeated string local_ports = 18;\n\n  // Identifier for the service or container port: either the name or port number\n  // optionally followed by a \"/TCP\" or \"/UDP\"\n  string port_identifier = 10;\n\n  // The resolved service port name\n  string service_port_name = 19;\n\n  // The resolved service port\n  int32 service_port = 20;\n\n  // .uid.metadata of service associated with intercept\n  string service_uid = 12;\n\n  // name of the aforementioned service\n  string service_name = 14;\n\n  // The resolved protocol used by the container port\n  string protocol = 21; // TCP or UDP\n\n  // Name of container that provides environment and mounts. This is normally the container\n  // that owns the container_port, but in some cases it will differ because the container_port\n  // is owned by some kind of routing mechanism (such as nginx).\n  string container_name = 24;\n\n  // The resolved container port that is intercepted.\n  int32 container_port = 23;\n\n  // The port on the workstation that the intercepted container_port is redirected to.\n  int32 target_port = 7;\n\n  // The delay imposed by a call roundtrip between the traffic-agent and\n  // the client on the workstation. This delay is added to the dial_timeout\n  // when the workstation performs a dial on behalf of the traffic-agent.\n  int64 roundtrip_latency = 16;\n\n  // The dial timeout to use when a dial is made on the intercepting workstation.\n  int64 dial_timeout = 17;\n\n  // Extra ports that will be forwarded from the intercepting client's localhost\n  // to the intercepted pod.\n  // Deprecated: use local_ports instead\n  repeated int32 extra_ports = 15;\n\n  // Whether to replace the running container.\n  bool replace = 22;\n\n  // place a wiretap on intercepted ports instead of redirecting them\n  bool wiretap = 26;\n\n  // Intercept desire no default port.\n  bool no_default_port = 25;\n\n  // HTTP header filters for HTTP Intercepts. Only requests containing\n  // these headers (key=value) will be intercepted. Multiple headers use AND logic.\n  map<string,string> header_filters = 27;\n\n  // Path filter patterns for HTTP Intercepts. Supports glob patterns like \"/api/v1/*\".\n  // If specified, only requests matching these paths will be intercepted.\n  repeated string path_filters = 28;\n\n  // Metadata to associate with the intercept. Retrievable using the API server.\n  map<string,string> metadata = 29;\n\n  // Plaintext instructs the traffic-agent to use plain text when communicating with the client.\n  bool plaintext = 30;\n\n  reserved 9; // used to be former mechanism_args\n\n  // Used to be mount_point and only utilized when passing the spec between\n  // the user daemon and the CLI. It's now moved to InterceptInfo\n  reserved 11;\n}\n\nenum InterceptDispositionType {\n  UNSPECIFIED = 0;\n\n  ACTIVE = 1;\n  WAITING = 2;\n  REMOVED = 9;\n\n  // Failure states\n\n  // What does \"NO_CLIENT\" mean?  The Manager garbage-collects the\n  // intercept if the client goes away.\n  NO_CLIENT = 3;\n\n  // NO_AGENT indicates that there are no currently-running agents\n  // that can service the intercept, or that there is a inconsistency\n  // between the agents that are running.  This may be an ephemeral\n  // state, such as inconsistency between agents during the middle of\n  // a rolling update.\n  NO_AGENT = 4;\n\n  // NO_MECHANISM indicates that the agent(s) that would handle this\n  // intercept do not report that they support the mechanism of the\n  // intercept.  For example, if you are running the OSS agent but ask\n  // for an intercept using the \"http\" mechanism, which requires the\n  // Ambassador Telepresence agent.\n  NO_MECHANISM = 5;\n\n  // NO_PORT indicates that the manager was unable to allocate a port\n  // to act as the rendezvous point between the client and the agent.\n  NO_PORTS = 6;\n\n  // AGENT_ERROR indicates that the intercept was submitted to an\n  // agent, but that the agent rejected it (by calling\n  // ReviewIntercept).\n  AGENT_ERROR = 7;\n\n  // BAD_ARGS indicates that something about the mechanism_args is\n  // invalid.\n  BAD_ARGS = 8;\n}\n\n// InterceptInfo contains information about a live intercept in an agent\nmessage InterceptInfo {\n  InterceptSpec spec = 1;\n\n  string id = 5;\n  SessionInfo client_session = 6;\n\n  // The current intercept state; a status code and a human-friendly\n  // message to go along with the status code.  These may be set\n  // manager itself, or may be set by the agent's call to\n  // ReviewIntercept.\n  InterceptDispositionType disposition = 3;\n  string message = 4;\n\n  // Name and port to use when establishing port-forward to the pod's\n  // gRPC API.\n  string pod_name = 19;\n  int32 api_port = 20;\n\n  string pod_ip = 10;\n  int32 sftp_port = 11;\n  int32 ftp_port = 18;\n\n  // The directory where the client mounts the remote mount_point. Only\n  // set when obtaining InterceptInfo from the user daemon.\n  string client_mount_point = 2;\n\n  // The directory where the intercept mounts can be found in the agent\n  string mount_point = 16;\n\n  // A human-friendly description of what the spec.mechanism_args say.\n  // This is set by the agent's call to ReviewIntercept.\n  string mechanism_args_desc = 12;\n\n  // The environment of the intercepted app\n  map<string, string> environment = 17;\n\n  // Map of mount path -> MountPolicy\n  map<string, int32> mounts = 22;\n\n  // Timestamp for last modification made by traffic-manager\n  google.protobuf.Timestamp modified_at = 21;\n\n  reserved 7; // was preview_domain\n\n  reserved 9; // was PreviewSpec preview_spec\n\n  reserved 13; // was api-key\n\n  reserved 14; // was headers used by the API-server\n\n  reserved 15; // was metadata used by the API-server\n}\n\nmessage ReconnectAgentRequest {\n  SessionInfo session = 1;\n  AgentInfo agent = 2;\n}\n\nmessage ReconnectClientRequest {\n  SessionInfo session = 1;\n  ClientInfo client = 2;\n  repeated InterceptInfo intercepts = 3;\n  repeated AgentInfo agents = 4;\n}\n\nmessage SessionInfo {\n  string session_id = 1;\n  string manager_install_id = 2;\n  optional string install_id = 3;\n}\n\nmessage AgentsRequest {\n  SessionInfo session = 1;\n  repeated string namespaces = 2;\n}\n\nmessage AgentInfoSnapshot {\n  repeated AgentInfo agents = 1;\n}\n\nmessage AgentInfoDelta {\n  map<string,AgentInfo>  upserts = 1;\n  repeated string removals = 2;\n}\n\nmessage InterceptInfoSnapshot {\n  repeated InterceptInfo intercepts = 1;\n}\n\nmessage InterceptInfoDelta {\n  map <string,InterceptInfo> upserts = 1;\n  repeated string removals = 2;\n}\n\nmessage CreateInterceptRequest {\n  SessionInfo session = 1;\n  InterceptSpec intercept_spec = 2;\n  reserved 3; // was api_key;\n}\n\nmessage EnsureAgentRequest {\n  SessionInfo session = 1;\n  string name = 2;\n}\n\nmessage PreparedIntercept {\n  string error = 1;\n  int32 error_category = 2;\n\n  string namespace = 3;\n  string service_uid = 4;\n  string service_name = 5;\n  string service_port_name = 6;\n  int32 service_port = 7;\n  string workload_kind = 8;\n  string agent_image = 9;\n  string protocol = 10; // TCP or UDP\n  string container_name = 11;\n  int32 container_port = 12;\n  repeated string pod_ports = 13;\n}\n\nmessage RemoveInterceptRequest2 {\n  SessionInfo session = 1;\n  string name = 2;\n}\n\nmessage GetInterceptRequest {\n  SessionInfo session = 1;\n  string name = 2;\n}\n\nmessage ReviewInterceptRequest {\n  SessionInfo session = 1;\n  string id = 2;\n  InterceptDispositionType disposition = 3;\n  string message = 4;\n\n  // pod IP and sftp port to use when doing sshfs mounts\n  string pod_ip = 5;\n  int32 sftp_port = 6;\n  int32 ftp_port = 12;\n\n  // The directory where the intercept mounts can be found in the agent\n  string mount_point = 10;\n\n  // A human-friendly description of what the\n  // InterceptSpec.mechanism_args say.\n  string mechanism_args_desc = 7;\n\n  // The environment of the intercepted container\n  map<string, string> environment = 11;\n\n  // Map of mount path -> MountPolicy of the engaged container\n  map<string, int32> mounts = 13;\n\n  reserved 8; // was headers used by the workstation API-server\n\n  reserved 9; // was metadata used by the workstation API-server\n}\n\nmessage RemainRequest {\n  SessionInfo session = 1;\n  google.protobuf.Timestamp last_activity = 3;\n  reserved 2; // was api_key\n}\n\nmessage LogLevelRequest {\n  string log_level = 1;\n\n  // The time that this log-level will be in effect before\n  // falling back to the configured log-level.\n  google.protobuf.Duration duration = 2;\n}\n\n// Deprecated.\nmessage GetLogsRequest {\n  // Whether or not logs from the traffic-manager are desired.\n  bool traffic_manager = 1;\n\n  // The traffic-agent(s) logs are desired from. Can be `all`, `False`,\n  // or substring to filter based on pod names.\n  string agents = 2;\n\n  // Whether or not to get the pod yaml deployed to the cluster.\n  bool get_pod_yaml = 3;\n}\n\n// Deprecated.\nmessage LogsResponse {\n\n  // The map contains assocations between <podName.namespace> and the logs\n  // from that pod.\n  map<string, string> pod_logs = 1;\n\n  // Errors encountered when getting logs from the traffic-manager\n  // and/or traffic-agents.\n  string err_msg = 2;\n\n  // The map contains assocations between <podName.namespace> and the pod's\n  // yaml.\n  map<string, string> pod_yaml = 3;\n}\n\nmessage TelepresenceAPIInfo {\n  // The port that the TelepresenceAPI is using, or 0 if it's not enabled\n  int32 port = 1;\n}\n\n// VersionInfo2 is different than telepresence.common.VersionInfo in\n// that it is limited to just name and version.\nmessage VersionInfo2 {\n  string name = 1;\n  string version = 2;\n}\n\n// TunnelMessage is a message sent over a Tunnel. First byte indicates type of message\nmessage TunnelMessage {\n  bytes payload = 1;\n}\n\nmessage DialRequest {\n  bytes conn_id = 1;\n  int64 roundtrip_latency = 2;\n  int64 dial_timeout = 3;\n  reserved 4;\n}\n\nmessage LookupRequest {\n  // Client session\n  SessionInfo session = 1;\n\n  // The name to use for the LookupIP request.\n  string name = 2;\n}\n\nmessage LookupResponse {\n  // List of found IPs in netip.Addr binary form.\n  repeated bytes ips = 1;\n}\n\n// LookupHost request sent from a client\nmessage DNSRequest {\n  // Client session\n  SessionInfo session = 1;\n  string name = 2;\n  uint32 type = 3;\n}\n\nmessage DNSResponse {\n  // DNS return code\n  int32 r_code = 1;\n\n  // rrs is an array of packed RR records\n  bytes rrs = 2;\n}\n\nmessage DNSAgentResponse {\n  // Agent session\n  SessionInfo session = 1;\n\n  // DNSRequest is the request that this is a response to\n  DNSRequest request = 2;\n\n  // The response, which might be nil in case no address was found\n  DNSResponse response = 3;\n}\n\n// IPNet is a subnet. e.g. 10.43.0.0/16\nmessage IPNet {\n  bytes ip = 1;\n  int32 mask = 2;\n}\n\n// ClusterInfo contains information that the root daemon needs in order to\n// establish outbound traffic to the cluster.\nmessage ClusterInfo {\n  // The service_cidrs reported by NetworkingV1().ServiceCIDRs() in netip.Prefix binary form.\n  repeated bytes service_cidrs = 1;\n\n  // service_subnet is the Kubernetes service subnet.\n  // Deprecated: use service_cidrs\n  IPNet service_subnet = 2;\n\n  // pod_subnets are the subnets used for Kubenetes pods.\n  repeated IPNet pod_subnets = 3;\n\n  // manager_pod_ip is the ip address of the traffic manager\n  bytes manager_pod_ip = 5;\n\n  // manager_pod_port is the port of the traffic manager\n  int32 manager_pod_port = 8;\n\n  // injector_svc_ip is the ip address of the traffic manager's agent injector service\n  bytes injector_svc_ip = 9;\n\n  // injector_svc_port is the port of the traffic manager's agent injector service\n  int32 injector_svc_port = 10;\n\n  // injector_svc_host is the http host of the traffic manager's agent injector service\n  string injector_svc_host = 11;\n\n  // Router configuration\n  Routing routing = 6;\n\n  // DNS configuration\n  DNS dns = 7;\n\n  // cluster_domain is the domain of the cluster, ending with a dot, e.g. \"cluster.local.\"\n  // Deprecated: Use dns.cluster_domain\n  reserved 4;\n}\n\nmessage Routing {\n  repeated IPNet also_proxy_subnets = 1;\n  repeated IPNet never_proxy_subnets = 2;\n  repeated IPNet allow_conflicting_subnets = 3;\n}\n\nmessage DNS {\n  repeated string include_suffixes = 1;\n  repeated string exclude_suffixes = 2;\n\n  // kube_dns_ip is the IP address of the kube-dns.kube-system service,\n  // Deprecated: No longer used by clients >= 2.8.0\n  bytes kube_ip = 3;\n\n  // cluster_domain is the domain of the cluster, ending with a dot, e.g. \"cluster.local.\"\n  string cluster_domain = 4;\n}\n\nmessage CLIConfig {\n  // config_yaml is a yaml blob containing the client config.\n  bytes config_yaml = 1;\n}\n\nmessage AgentImageFQN {\n  string f_q_n = 1;\n}\n\nmessage AgentPodInfo {\n  string pod_id = 7;\n  string pod_name = 1;\n  string namespace = 2;\n  bytes pod_ip = 3;\n  int32 api_port = 4;\n  bool intercepted = 5;\n  string workload_name = 6;\n}\n\nmessage AgentPodInfoSnapshot {\n  repeated AgentPodInfo agents = 1;\n}\n\nmessage AgentPodInfoDelta {\n  map<string,AgentPodInfo> upserts = 1;\n  repeated string removals = 2;\n}\n\nmessage AgentConfigRequest {\n  SessionInfo session = 1;\n  string name = 2;\n}\n\nmessage AgentConfigResponse {\n  bytes data = 1;\n}\n\nmessage TunnelMetrics {\n  string client_session_id = 1;\n\n  // Number of bytes sent from the client to the traffic-agent.\n  uint64 ingress_bytes = 2;\n\n  // Number of bytes sent from traffic-agent to the client.\n  uint64 egress_bytes = 3;\n}\n\nmessage KnownWorkloadKinds {\n  repeated WorkloadInfo.Kind kinds = 1;\n}\n\n// WorkloadInfo contains information about a workload (typically a\n// Deployment).\nmessage WorkloadInfo {\n  enum Kind {\n    UNSPECIFIED = 0;\n    DEPLOYMENT = 1;\n    REPLICASET = 2;\n    STATEFULSET = 3;\n    ROLLOUT = 4;\n  }\n\n  enum State {\n    // The state of this workload is not known.\n    UNKNOWN_UNSPECIFIED = 0;\n\n    // Available means the deployment is available, ie. at least the minimum available\n    // replicas required are up and running for at least minReadySeconds.\n    AVAILABLE = 1;\n\n    // Progressing means the workload is progressing. Progress for a workload is\n    // considered when a new replica set is created or adopted, and when new pods scale\n    // up or old pods scale down. Progress is not estimated for paused workloads or\n    // when progressDeadlineSeconds is not specified.\n    PROGRESSING = 2;\n\n    // FAILURE means that one of its pods fails to be created or deleted.\n    FAILURE = 3;\n  }\n\n  enum AgentState {\n    // Workload has never been intercepted, so no agent has been installed.\n    NO_AGENT_UNSPECIFIED = 0;\n\n    // An agent has been installed into workload's pods, but it is not currently intercepted.\n    INSTALLED   = 1;\n\n    // The workload (or rather its pods) is currently intercepted.\n    INTERCEPTED = 2;\n  }\n\n  message Intercept {\n    // name of intercepting client\n    string client = 1;\n  }\n\n  Kind kind = 1;\n  string name = 2;\n  string namespace = 3;\n  string uid = 7;\n  AgentState agent_state = 4;\n\n  repeated Intercept intercept_clients = 5;\n\n  State state = 6;\n}\n\nmessage WorkloadEvent {\n  enum Type {\n    ADDED_UNSPECIFIED = 0;\n    MODIFIED = 1;\n    DELETED = 2;\n  }\n\n  Type type = 1;\n  WorkloadInfo workload = 2;\n}\n// WorkloadEventDelta contains the changes made to the subscribed namespace since\n// the time given in the timestamp. A watcher can rely on that received deltas are\n// consecutive.\nmessage WorkloadEventsDelta {\n  // The timestamp from which this delta is computed. Typically\n  // equal to the time when the previous delta was sent.\n  google.protobuf.Timestamp since = 1;\n\n  repeated WorkloadEvent events = 2;\n}\n\nmessage WorkloadEventsRequest {\n  // The session_info identifies the client connection, and hence the\n  // namespace for the resulting watcher.\n  SessionInfo session_info = 1;\n\n  // The timestamp from which the first delta should be computed. Set to\n  // undefined to get a delta that contains everything.\n  google.protobuf.Timestamp since = 2;\n\n  // The namespace to watch. Must be one of the namespaces that are\n  // managed by the traffic-manager. Defaults to the connected namespace.\n  string namespace = 3;\n}\n\nmessage UninstallAgentsRequest {\n  // The session_info identifies the client connection, and hence the\n  // namespace for the resulting watcher.\n  SessionInfo session_info = 1;\n\n  // The agents to install. Empty means all agents in the connected namespace.\n  repeated string agents = 2;\n}\n\nservice Manager {\n  // Version returns the version information of the Manager.\n  rpc Version(google.protobuf.Empty) returns (VersionInfo2);\n\n  // GetAgentImageFQN returns fully qualified name of the image that is injected into intercepted pods.\n  rpc GetAgentImageFQN(google.protobuf.Empty) returns (AgentImageFQN);\n\n  // GetAgentConfig returns the agent configuration for a specific workload.\n  rpc GetAgentConfig(AgentConfigRequest) returns (AgentConfigResponse);\n\n  // GetClientConfig returns the config that connected clients should use for this manager.\n  rpc GetClientConfig(google.protobuf.Empty) returns (CLIConfig);\n\n  // GetTelepresenceAPI returns information about the TelepresenceAPI server\n  rpc GetTelepresenceAPI(google.protobuf.Empty) returns (TelepresenceAPIInfo);\n\n  // ArriveAsClient establishes a session between a client and the Manager.\n  rpc ArriveAsClient(ClientInfo) returns (SessionInfo);\n\n  // ReconnectAgent re-establishes a session between an agent and the Manager.\n  rpc ReconnectAgent(ReconnectAgentRequest) returns (google.protobuf.Empty);\n\n  // ReconnectClient re-establishes a session between a client and the Manager.\n  rpc ReconnectClient(ReconnectClientRequest) returns (google.protobuf.Empty);\n\n  // ArriveAsAgent establishes a session between an agent and the Manager.\n  rpc ArriveAsAgent(AgentInfo) returns (SessionInfo);\n\n  // Remain indicates that the session is still valid, and potentially\n  // updates the auth token for the session.\n  rpc Remain(RemainRequest) returns (google.protobuf.Empty);\n\n  // Depart terminates a session.\n  rpc Depart(SessionInfo) returns (google.protobuf.Empty);\n\n  // SetLogLevel will temporarily set the log-level for the traffic-manager and all\n  // traffic-agents for a duration that is determined b the request.\n  rpc SetLogLevel(LogLevelRequest) returns (google.protobuf.Empty);\n\n  // GetLogs will acquire logs for the various Telepresence components in kubernetes\n  // (pending the request) and return them to the caller\n  // Deprecated: Will return an empty response\n  rpc GetLogs(GetLogsRequest) returns (LogsResponse);\n\n  // WatchAgentPods notifies a client of the set of known Agents from the client\n  // connections namespace that the client can connect to when port-forwards are\n  // allowed.\n  rpc WatchAgentPods(SessionInfo) returns (stream AgentPodInfoSnapshot);\n\n  // WatchAgentPods notifies a client of changes to the set of known Agents from the client\n  // connections namespace that the client can connect to when port-forwards are\n  // allowed.\n  rpc WatchAgentPodsDelta(SessionInfo) returns (stream AgentPodInfoDelta);\n\n  // WatchAgents notifies a client of the set of known Agents.\n  //\n  // A session ID is required; if no session ID is given then the call\n  // returns immediately, having not delivered any snapshots.\n  rpc WatchAgents(SessionInfo) returns (stream AgentInfoSnapshot);\n\n  // WatchAgents notifies a client of changes to the set of known Agents.\n  rpc WatchAgentsDelta(SessionInfo) returns (stream AgentInfoDelta);\n\n  // WatchIntercepts notifies a client or agent of the set of intercepts\n  // relevant to that client or agent.\n  //\n  // If a session ID is given, then only intercepts associated with\n  // that session are watched.  If no session ID is given, then all\n  // intercepts are watched.\n  rpc WatchIntercepts(SessionInfo) returns (stream InterceptInfoSnapshot);\n\n  // WatchInterceptsDelta notifies a client or agent of changes in the set of intercepts\n  // relevant to that client or agent.\n  rpc WatchInterceptsDelta(SessionInfo) returns (stream InterceptInfoDelta);\n\n  // WatchWorkloads notifies a client of the set of Workloads from the client\n  // connection's namespace.\n  rpc WatchWorkloads(WorkloadEventsRequest) returns (stream WorkloadEventsDelta);\n\n  // WatchClusterInfo returns information needed when establishing\n  // connectivity to the cluster.\n  rpc WatchClusterInfo(SessionInfo) returns (stream ClusterInfo);\n\n  // EnsureAgent ensures that an agent is injected to the pods of a workload and\n  // returns the agents, sorted by pod name.\n  rpc EnsureAgent(EnsureAgentRequest) returns (AgentInfoSnapshot);\n\n  // Request that the traffic-manager makes the preparations necessary to\n  // create the given intercept.\n  rpc PrepareIntercept(CreateInterceptRequest) returns (PreparedIntercept);\n\n  // CreateIntercept lets a client create an intercept.  It will be\n  // created in the \"WATING\" disposition, and it will remain in that\n  // state until the Agent (the app-sidecar) calls ReviewIntercept()\n  // to transition it to the \"ACTIVE\" disposition (or one of the error\n  // dispositions).\n  rpc CreateIntercept(CreateInterceptRequest) returns (InterceptInfo);\n\n  // RemoveIntercept lets a client remove an intercept.\n  rpc RemoveIntercept(RemoveInterceptRequest2) returns (google.protobuf.Empty);\n\n  // GetIntercept gets info from intercept name\n  rpc GetIntercept(GetInterceptRequest) returns (InterceptInfo);\n\n  // ReviewIntercept lets an agent approve or reject an intercept by\n  // changing the disposition from \"WATING\" to \"ACTIVE\" or to an\n  // error, and setting a human-readable status message.\n  rpc ReviewIntercept(ReviewInterceptRequest) returns (google.protobuf.Empty);\n\n  // GetKnownWorkloadKinds returns the known workload kinds\n  // that the manager can handle. This set may include Deployment, StatefulSet, ReplicaSet, Rollout (Argo Rollouts)\n  // as configured in the manager's Helm values.\n  rpc GetKnownWorkloadKinds(SessionInfo) returns (KnownWorkloadKinds);\n\n  // Lookup performs an LookupIP in the cluster and returns the resultings IPs.\n  rpc Lookup(LookupRequest) returns (LookupResponse);\n\n  // LookupDNS performs a DNS lookup in the cluster. If the caller has intercepts\n  // active, the lookup will be performed from the intercepted pods.\n  rpc LookupDNS(DNSRequest) returns (DNSResponse);\n\n  // WatchLogLevel lets an agent receive log-level updates\n  rpc WatchLogLevel(google.protobuf.Empty) returns (stream LogLevelRequest);\n\n  // A Tunnel represents one single connection where the client or\n  // traffic-agent represents one end (the client-side) and the\n  // traffic-manager represents the other (the server side). The first\n  // message that a client sends when the tunnel is established is will\n  // always contain the session ID, connection ID, and timeouts used by\n  // the dialer endpoints.\n  rpc Tunnel(stream TunnelMessage) returns (stream TunnelMessage);\n\n  // ReportMetrics is used by a traffic-agent to report metrics for streams\n  // established when clients connect directly to traffic-agents using port-forward.\n  rpc ReportMetrics(TunnelMetrics) returns (google.protobuf.Empty);\n\n  // UninstallAgents will uninstall the traffic-agent from the given workloads (or all\n  // workloads if the list is empty).\n  rpc UninstallAgents(UninstallAgentsRequest) returns (google.protobuf.Empty);\n}\n"
  },
  {
    "path": "rpc/manager/manager_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.1\n// - protoc             v6.30.2\n// source: manager/manager.proto\n\n// The \"manager\" package describes the server implemented by the\n// in-cluster Manager, which is spoken to by the Agent (app-sidecar),\n// the on-laptop Connector (user-daemon), and the on-laptop CLI.\n\npackage manager\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tManager_Version_FullMethodName               = \"/telepresence.manager.Manager/Version\"\n\tManager_GetAgentImageFQN_FullMethodName      = \"/telepresence.manager.Manager/GetAgentImageFQN\"\n\tManager_GetAgentConfig_FullMethodName        = \"/telepresence.manager.Manager/GetAgentConfig\"\n\tManager_GetClientConfig_FullMethodName       = \"/telepresence.manager.Manager/GetClientConfig\"\n\tManager_GetTelepresenceAPI_FullMethodName    = \"/telepresence.manager.Manager/GetTelepresenceAPI\"\n\tManager_ArriveAsClient_FullMethodName        = \"/telepresence.manager.Manager/ArriveAsClient\"\n\tManager_ReconnectAgent_FullMethodName        = \"/telepresence.manager.Manager/ReconnectAgent\"\n\tManager_ReconnectClient_FullMethodName       = \"/telepresence.manager.Manager/ReconnectClient\"\n\tManager_ArriveAsAgent_FullMethodName         = \"/telepresence.manager.Manager/ArriveAsAgent\"\n\tManager_Remain_FullMethodName                = \"/telepresence.manager.Manager/Remain\"\n\tManager_Depart_FullMethodName                = \"/telepresence.manager.Manager/Depart\"\n\tManager_SetLogLevel_FullMethodName           = \"/telepresence.manager.Manager/SetLogLevel\"\n\tManager_GetLogs_FullMethodName               = \"/telepresence.manager.Manager/GetLogs\"\n\tManager_WatchAgentPods_FullMethodName        = \"/telepresence.manager.Manager/WatchAgentPods\"\n\tManager_WatchAgentPodsDelta_FullMethodName   = \"/telepresence.manager.Manager/WatchAgentPodsDelta\"\n\tManager_WatchAgents_FullMethodName           = \"/telepresence.manager.Manager/WatchAgents\"\n\tManager_WatchAgentsDelta_FullMethodName      = \"/telepresence.manager.Manager/WatchAgentsDelta\"\n\tManager_WatchIntercepts_FullMethodName       = \"/telepresence.manager.Manager/WatchIntercepts\"\n\tManager_WatchInterceptsDelta_FullMethodName  = \"/telepresence.manager.Manager/WatchInterceptsDelta\"\n\tManager_WatchWorkloads_FullMethodName        = \"/telepresence.manager.Manager/WatchWorkloads\"\n\tManager_WatchClusterInfo_FullMethodName      = \"/telepresence.manager.Manager/WatchClusterInfo\"\n\tManager_EnsureAgent_FullMethodName           = \"/telepresence.manager.Manager/EnsureAgent\"\n\tManager_PrepareIntercept_FullMethodName      = \"/telepresence.manager.Manager/PrepareIntercept\"\n\tManager_CreateIntercept_FullMethodName       = \"/telepresence.manager.Manager/CreateIntercept\"\n\tManager_RemoveIntercept_FullMethodName       = \"/telepresence.manager.Manager/RemoveIntercept\"\n\tManager_GetIntercept_FullMethodName          = \"/telepresence.manager.Manager/GetIntercept\"\n\tManager_ReviewIntercept_FullMethodName       = \"/telepresence.manager.Manager/ReviewIntercept\"\n\tManager_GetKnownWorkloadKinds_FullMethodName = \"/telepresence.manager.Manager/GetKnownWorkloadKinds\"\n\tManager_Lookup_FullMethodName                = \"/telepresence.manager.Manager/Lookup\"\n\tManager_LookupDNS_FullMethodName             = \"/telepresence.manager.Manager/LookupDNS\"\n\tManager_WatchLogLevel_FullMethodName         = \"/telepresence.manager.Manager/WatchLogLevel\"\n\tManager_Tunnel_FullMethodName                = \"/telepresence.manager.Manager/Tunnel\"\n\tManager_ReportMetrics_FullMethodName         = \"/telepresence.manager.Manager/ReportMetrics\"\n\tManager_UninstallAgents_FullMethodName       = \"/telepresence.manager.Manager/UninstallAgents\"\n)\n\n// ManagerClient is the client API for Manager service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype ManagerClient interface {\n\t// Version returns the version information of the Manager.\n\tVersion(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*VersionInfo2, error)\n\t// GetAgentImageFQN returns fully qualified name of the image that is injected into intercepted pods.\n\tGetAgentImageFQN(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*AgentImageFQN, error)\n\t// GetAgentConfig returns the agent configuration for a specific workload.\n\tGetAgentConfig(ctx context.Context, in *AgentConfigRequest, opts ...grpc.CallOption) (*AgentConfigResponse, error)\n\t// GetClientConfig returns the config that connected clients should use for this manager.\n\tGetClientConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CLIConfig, error)\n\t// GetTelepresenceAPI returns information about the TelepresenceAPI server\n\tGetTelepresenceAPI(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TelepresenceAPIInfo, error)\n\t// ArriveAsClient establishes a session between a client and the Manager.\n\tArriveAsClient(ctx context.Context, in *ClientInfo, opts ...grpc.CallOption) (*SessionInfo, error)\n\t// ReconnectAgent re-establishes a session between an agent and the Manager.\n\tReconnectAgent(ctx context.Context, in *ReconnectAgentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// ReconnectClient re-establishes a session between a client and the Manager.\n\tReconnectClient(ctx context.Context, in *ReconnectClientRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// ArriveAsAgent establishes a session between an agent and the Manager.\n\tArriveAsAgent(ctx context.Context, in *AgentInfo, opts ...grpc.CallOption) (*SessionInfo, error)\n\t// Remain indicates that the session is still valid, and potentially\n\t// updates the auth token for the session.\n\tRemain(ctx context.Context, in *RemainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// Depart terminates a session.\n\tDepart(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// SetLogLevel will temporarily set the log-level for the traffic-manager and all\n\t// traffic-agents for a duration that is determined b the request.\n\tSetLogLevel(ctx context.Context, in *LogLevelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// GetLogs will acquire logs for the various Telepresence components in kubernetes\n\t// (pending the request) and return them to the caller\n\t// Deprecated: Will return an empty response\n\tGetLogs(ctx context.Context, in *GetLogsRequest, opts ...grpc.CallOption) (*LogsResponse, error)\n\t// WatchAgentPods notifies a client of the set of known Agents from the client\n\t// connections namespace that the client can connect to when port-forwards are\n\t// allowed.\n\tWatchAgentPods(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AgentPodInfoSnapshot], error)\n\t// WatchAgentPods notifies a client of changes to the set of known Agents from the client\n\t// connections namespace that the client can connect to when port-forwards are\n\t// allowed.\n\tWatchAgentPodsDelta(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AgentPodInfoDelta], error)\n\t// WatchAgents notifies a client of the set of known Agents.\n\t//\n\t// A session ID is required; if no session ID is given then the call\n\t// returns immediately, having not delivered any snapshots.\n\tWatchAgents(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AgentInfoSnapshot], error)\n\t// WatchAgents notifies a client of changes to the set of known Agents.\n\tWatchAgentsDelta(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AgentInfoDelta], error)\n\t// WatchIntercepts notifies a client or agent of the set of intercepts\n\t// relevant to that client or agent.\n\t//\n\t// If a session ID is given, then only intercepts associated with\n\t// that session are watched.  If no session ID is given, then all\n\t// intercepts are watched.\n\tWatchIntercepts(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[InterceptInfoSnapshot], error)\n\t// WatchInterceptsDelta notifies a client or agent of changes in the set of intercepts\n\t// relevant to that client or agent.\n\tWatchInterceptsDelta(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[InterceptInfoDelta], error)\n\t// WatchWorkloads notifies a client of the set of Workloads from the client\n\t// connection's namespace.\n\tWatchWorkloads(ctx context.Context, in *WorkloadEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WorkloadEventsDelta], error)\n\t// WatchClusterInfo returns information needed when establishing\n\t// connectivity to the cluster.\n\tWatchClusterInfo(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClusterInfo], error)\n\t// EnsureAgent ensures that an agent is injected to the pods of a workload and\n\t// returns the agents, sorted by pod name.\n\tEnsureAgent(ctx context.Context, in *EnsureAgentRequest, opts ...grpc.CallOption) (*AgentInfoSnapshot, error)\n\t// Request that the traffic-manager makes the preparations necessary to\n\t// create the given intercept.\n\tPrepareIntercept(ctx context.Context, in *CreateInterceptRequest, opts ...grpc.CallOption) (*PreparedIntercept, error)\n\t// CreateIntercept lets a client create an intercept.  It will be\n\t// created in the \"WATING\" disposition, and it will remain in that\n\t// state until the Agent (the app-sidecar) calls ReviewIntercept()\n\t// to transition it to the \"ACTIVE\" disposition (or one of the error\n\t// dispositions).\n\tCreateIntercept(ctx context.Context, in *CreateInterceptRequest, opts ...grpc.CallOption) (*InterceptInfo, error)\n\t// RemoveIntercept lets a client remove an intercept.\n\tRemoveIntercept(ctx context.Context, in *RemoveInterceptRequest2, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// GetIntercept gets info from intercept name\n\tGetIntercept(ctx context.Context, in *GetInterceptRequest, opts ...grpc.CallOption) (*InterceptInfo, error)\n\t// ReviewIntercept lets an agent approve or reject an intercept by\n\t// changing the disposition from \"WATING\" to \"ACTIVE\" or to an\n\t// error, and setting a human-readable status message.\n\tReviewIntercept(ctx context.Context, in *ReviewInterceptRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// GetKnownWorkloadKinds returns the known workload kinds\n\t// that the manager can handle. This set may include Deployment, StatefulSet, ReplicaSet, Rollout (Argo Rollouts)\n\t// as configured in the manager's Helm values.\n\tGetKnownWorkloadKinds(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (*KnownWorkloadKinds, error)\n\t// Lookup performs an LookupIP in the cluster and returns the resultings IPs.\n\tLookup(ctx context.Context, in *LookupRequest, opts ...grpc.CallOption) (*LookupResponse, error)\n\t// LookupDNS performs a DNS lookup in the cluster. If the caller has intercepts\n\t// active, the lookup will be performed from the intercepted pods.\n\tLookupDNS(ctx context.Context, in *DNSRequest, opts ...grpc.CallOption) (*DNSResponse, error)\n\t// WatchLogLevel lets an agent receive log-level updates\n\tWatchLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogLevelRequest], error)\n\t// A Tunnel represents one single connection where the client or\n\t// traffic-agent represents one end (the client-side) and the\n\t// traffic-manager represents the other (the server side). The first\n\t// message that a client sends when the tunnel is established is will\n\t// always contain the session ID, connection ID, and timeouts used by\n\t// the dialer endpoints.\n\tTunnel(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TunnelMessage, TunnelMessage], error)\n\t// ReportMetrics is used by a traffic-agent to report metrics for streams\n\t// established when clients connect directly to traffic-agents using port-forward.\n\tReportMetrics(ctx context.Context, in *TunnelMetrics, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\t// UninstallAgents will uninstall the traffic-agent from the given workloads (or all\n\t// workloads if the list is empty).\n\tUninstallAgents(ctx context.Context, in *UninstallAgentsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n}\n\ntype managerClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewManagerClient(cc grpc.ClientConnInterface) ManagerClient {\n\treturn &managerClient{cc}\n}\n\nfunc (c *managerClient) Version(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*VersionInfo2, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(VersionInfo2)\n\terr := c.cc.Invoke(ctx, Manager_Version_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) GetAgentImageFQN(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*AgentImageFQN, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AgentImageFQN)\n\terr := c.cc.Invoke(ctx, Manager_GetAgentImageFQN_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) GetAgentConfig(ctx context.Context, in *AgentConfigRequest, opts ...grpc.CallOption) (*AgentConfigResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AgentConfigResponse)\n\terr := c.cc.Invoke(ctx, Manager_GetAgentConfig_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) GetClientConfig(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*CLIConfig, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(CLIConfig)\n\terr := c.cc.Invoke(ctx, Manager_GetClientConfig_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) GetTelepresenceAPI(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*TelepresenceAPIInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(TelepresenceAPIInfo)\n\terr := c.cc.Invoke(ctx, Manager_GetTelepresenceAPI_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) ArriveAsClient(ctx context.Context, in *ClientInfo, opts ...grpc.CallOption) (*SessionInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SessionInfo)\n\terr := c.cc.Invoke(ctx, Manager_ArriveAsClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) ReconnectAgent(ctx context.Context, in *ReconnectAgentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_ReconnectAgent_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) ReconnectClient(ctx context.Context, in *ReconnectClientRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_ReconnectClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) ArriveAsAgent(ctx context.Context, in *AgentInfo, opts ...grpc.CallOption) (*SessionInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(SessionInfo)\n\terr := c.cc.Invoke(ctx, Manager_ArriveAsAgent_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) Remain(ctx context.Context, in *RemainRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_Remain_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) Depart(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_Depart_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) SetLogLevel(ctx context.Context, in *LogLevelRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_SetLogLevel_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) GetLogs(ctx context.Context, in *GetLogsRequest, opts ...grpc.CallOption) (*LogsResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(LogsResponse)\n\terr := c.cc.Invoke(ctx, Manager_GetLogs_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) WatchAgentPods(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AgentPodInfoSnapshot], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[0], Manager_WatchAgentPods_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SessionInfo, AgentPodInfoSnapshot]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchAgentPodsClient = grpc.ServerStreamingClient[AgentPodInfoSnapshot]\n\nfunc (c *managerClient) WatchAgentPodsDelta(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AgentPodInfoDelta], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[1], Manager_WatchAgentPodsDelta_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SessionInfo, AgentPodInfoDelta]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchAgentPodsDeltaClient = grpc.ServerStreamingClient[AgentPodInfoDelta]\n\nfunc (c *managerClient) WatchAgents(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AgentInfoSnapshot], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[2], Manager_WatchAgents_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SessionInfo, AgentInfoSnapshot]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchAgentsClient = grpc.ServerStreamingClient[AgentInfoSnapshot]\n\nfunc (c *managerClient) WatchAgentsDelta(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[AgentInfoDelta], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[3], Manager_WatchAgentsDelta_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SessionInfo, AgentInfoDelta]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchAgentsDeltaClient = grpc.ServerStreamingClient[AgentInfoDelta]\n\nfunc (c *managerClient) WatchIntercepts(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[InterceptInfoSnapshot], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[4], Manager_WatchIntercepts_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SessionInfo, InterceptInfoSnapshot]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchInterceptsClient = grpc.ServerStreamingClient[InterceptInfoSnapshot]\n\nfunc (c *managerClient) WatchInterceptsDelta(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[InterceptInfoDelta], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[5], Manager_WatchInterceptsDelta_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SessionInfo, InterceptInfoDelta]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchInterceptsDeltaClient = grpc.ServerStreamingClient[InterceptInfoDelta]\n\nfunc (c *managerClient) WatchWorkloads(ctx context.Context, in *WorkloadEventsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[WorkloadEventsDelta], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[6], Manager_WatchWorkloads_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[WorkloadEventsRequest, WorkloadEventsDelta]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchWorkloadsClient = grpc.ServerStreamingClient[WorkloadEventsDelta]\n\nfunc (c *managerClient) WatchClusterInfo(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ClusterInfo], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[7], Manager_WatchClusterInfo_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[SessionInfo, ClusterInfo]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchClusterInfoClient = grpc.ServerStreamingClient[ClusterInfo]\n\nfunc (c *managerClient) EnsureAgent(ctx context.Context, in *EnsureAgentRequest, opts ...grpc.CallOption) (*AgentInfoSnapshot, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(AgentInfoSnapshot)\n\terr := c.cc.Invoke(ctx, Manager_EnsureAgent_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) PrepareIntercept(ctx context.Context, in *CreateInterceptRequest, opts ...grpc.CallOption) (*PreparedIntercept, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(PreparedIntercept)\n\terr := c.cc.Invoke(ctx, Manager_PrepareIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) CreateIntercept(ctx context.Context, in *CreateInterceptRequest, opts ...grpc.CallOption) (*InterceptInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(InterceptInfo)\n\terr := c.cc.Invoke(ctx, Manager_CreateIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) RemoveIntercept(ctx context.Context, in *RemoveInterceptRequest2, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_RemoveIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) GetIntercept(ctx context.Context, in *GetInterceptRequest, opts ...grpc.CallOption) (*InterceptInfo, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(InterceptInfo)\n\terr := c.cc.Invoke(ctx, Manager_GetIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) ReviewIntercept(ctx context.Context, in *ReviewInterceptRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_ReviewIntercept_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) GetKnownWorkloadKinds(ctx context.Context, in *SessionInfo, opts ...grpc.CallOption) (*KnownWorkloadKinds, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(KnownWorkloadKinds)\n\terr := c.cc.Invoke(ctx, Manager_GetKnownWorkloadKinds_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) Lookup(ctx context.Context, in *LookupRequest, opts ...grpc.CallOption) (*LookupResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(LookupResponse)\n\terr := c.cc.Invoke(ctx, Manager_Lookup_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) LookupDNS(ctx context.Context, in *DNSRequest, opts ...grpc.CallOption) (*DNSResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DNSResponse)\n\terr := c.cc.Invoke(ctx, Manager_LookupDNS_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) WatchLogLevel(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogLevelRequest], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[8], Manager_WatchLogLevel_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[emptypb.Empty, LogLevelRequest]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchLogLevelClient = grpc.ServerStreamingClient[LogLevelRequest]\n\nfunc (c *managerClient) Tunnel(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[TunnelMessage, TunnelMessage], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Manager_ServiceDesc.Streams[9], Manager_Tunnel_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[TunnelMessage, TunnelMessage]{ClientStream: stream}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_TunnelClient = grpc.BidiStreamingClient[TunnelMessage, TunnelMessage]\n\nfunc (c *managerClient) ReportMetrics(ctx context.Context, in *TunnelMetrics, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_ReportMetrics_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *managerClient) UninstallAgents(ctx context.Context, in *UninstallAgentsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Manager_UninstallAgents_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// ManagerServer is the server API for Manager service.\n// All implementations must embed UnimplementedManagerServer\n// for forward compatibility.\ntype ManagerServer interface {\n\t// Version returns the version information of the Manager.\n\tVersion(context.Context, *emptypb.Empty) (*VersionInfo2, error)\n\t// GetAgentImageFQN returns fully qualified name of the image that is injected into intercepted pods.\n\tGetAgentImageFQN(context.Context, *emptypb.Empty) (*AgentImageFQN, error)\n\t// GetAgentConfig returns the agent configuration for a specific workload.\n\tGetAgentConfig(context.Context, *AgentConfigRequest) (*AgentConfigResponse, error)\n\t// GetClientConfig returns the config that connected clients should use for this manager.\n\tGetClientConfig(context.Context, *emptypb.Empty) (*CLIConfig, error)\n\t// GetTelepresenceAPI returns information about the TelepresenceAPI server\n\tGetTelepresenceAPI(context.Context, *emptypb.Empty) (*TelepresenceAPIInfo, error)\n\t// ArriveAsClient establishes a session between a client and the Manager.\n\tArriveAsClient(context.Context, *ClientInfo) (*SessionInfo, error)\n\t// ReconnectAgent re-establishes a session between an agent and the Manager.\n\tReconnectAgent(context.Context, *ReconnectAgentRequest) (*emptypb.Empty, error)\n\t// ReconnectClient re-establishes a session between a client and the Manager.\n\tReconnectClient(context.Context, *ReconnectClientRequest) (*emptypb.Empty, error)\n\t// ArriveAsAgent establishes a session between an agent and the Manager.\n\tArriveAsAgent(context.Context, *AgentInfo) (*SessionInfo, error)\n\t// Remain indicates that the session is still valid, and potentially\n\t// updates the auth token for the session.\n\tRemain(context.Context, *RemainRequest) (*emptypb.Empty, error)\n\t// Depart terminates a session.\n\tDepart(context.Context, *SessionInfo) (*emptypb.Empty, error)\n\t// SetLogLevel will temporarily set the log-level for the traffic-manager and all\n\t// traffic-agents for a duration that is determined b the request.\n\tSetLogLevel(context.Context, *LogLevelRequest) (*emptypb.Empty, error)\n\t// GetLogs will acquire logs for the various Telepresence components in kubernetes\n\t// (pending the request) and return them to the caller\n\t// Deprecated: Will return an empty response\n\tGetLogs(context.Context, *GetLogsRequest) (*LogsResponse, error)\n\t// WatchAgentPods notifies a client of the set of known Agents from the client\n\t// connections namespace that the client can connect to when port-forwards are\n\t// allowed.\n\tWatchAgentPods(*SessionInfo, grpc.ServerStreamingServer[AgentPodInfoSnapshot]) error\n\t// WatchAgentPods notifies a client of changes to the set of known Agents from the client\n\t// connections namespace that the client can connect to when port-forwards are\n\t// allowed.\n\tWatchAgentPodsDelta(*SessionInfo, grpc.ServerStreamingServer[AgentPodInfoDelta]) error\n\t// WatchAgents notifies a client of the set of known Agents.\n\t//\n\t// A session ID is required; if no session ID is given then the call\n\t// returns immediately, having not delivered any snapshots.\n\tWatchAgents(*SessionInfo, grpc.ServerStreamingServer[AgentInfoSnapshot]) error\n\t// WatchAgents notifies a client of changes to the set of known Agents.\n\tWatchAgentsDelta(*SessionInfo, grpc.ServerStreamingServer[AgentInfoDelta]) error\n\t// WatchIntercepts notifies a client or agent of the set of intercepts\n\t// relevant to that client or agent.\n\t//\n\t// If a session ID is given, then only intercepts associated with\n\t// that session are watched.  If no session ID is given, then all\n\t// intercepts are watched.\n\tWatchIntercepts(*SessionInfo, grpc.ServerStreamingServer[InterceptInfoSnapshot]) error\n\t// WatchInterceptsDelta notifies a client or agent of changes in the set of intercepts\n\t// relevant to that client or agent.\n\tWatchInterceptsDelta(*SessionInfo, grpc.ServerStreamingServer[InterceptInfoDelta]) error\n\t// WatchWorkloads notifies a client of the set of Workloads from the client\n\t// connection's namespace.\n\tWatchWorkloads(*WorkloadEventsRequest, grpc.ServerStreamingServer[WorkloadEventsDelta]) error\n\t// WatchClusterInfo returns information needed when establishing\n\t// connectivity to the cluster.\n\tWatchClusterInfo(*SessionInfo, grpc.ServerStreamingServer[ClusterInfo]) error\n\t// EnsureAgent ensures that an agent is injected to the pods of a workload and\n\t// returns the agents, sorted by pod name.\n\tEnsureAgent(context.Context, *EnsureAgentRequest) (*AgentInfoSnapshot, error)\n\t// Request that the traffic-manager makes the preparations necessary to\n\t// create the given intercept.\n\tPrepareIntercept(context.Context, *CreateInterceptRequest) (*PreparedIntercept, error)\n\t// CreateIntercept lets a client create an intercept.  It will be\n\t// created in the \"WATING\" disposition, and it will remain in that\n\t// state until the Agent (the app-sidecar) calls ReviewIntercept()\n\t// to transition it to the \"ACTIVE\" disposition (or one of the error\n\t// dispositions).\n\tCreateIntercept(context.Context, *CreateInterceptRequest) (*InterceptInfo, error)\n\t// RemoveIntercept lets a client remove an intercept.\n\tRemoveIntercept(context.Context, *RemoveInterceptRequest2) (*emptypb.Empty, error)\n\t// GetIntercept gets info from intercept name\n\tGetIntercept(context.Context, *GetInterceptRequest) (*InterceptInfo, error)\n\t// ReviewIntercept lets an agent approve or reject an intercept by\n\t// changing the disposition from \"WATING\" to \"ACTIVE\" or to an\n\t// error, and setting a human-readable status message.\n\tReviewIntercept(context.Context, *ReviewInterceptRequest) (*emptypb.Empty, error)\n\t// GetKnownWorkloadKinds returns the known workload kinds\n\t// that the manager can handle. This set may include Deployment, StatefulSet, ReplicaSet, Rollout (Argo Rollouts)\n\t// as configured in the manager's Helm values.\n\tGetKnownWorkloadKinds(context.Context, *SessionInfo) (*KnownWorkloadKinds, error)\n\t// Lookup performs an LookupIP in the cluster and returns the resultings IPs.\n\tLookup(context.Context, *LookupRequest) (*LookupResponse, error)\n\t// LookupDNS performs a DNS lookup in the cluster. If the caller has intercepts\n\t// active, the lookup will be performed from the intercepted pods.\n\tLookupDNS(context.Context, *DNSRequest) (*DNSResponse, error)\n\t// WatchLogLevel lets an agent receive log-level updates\n\tWatchLogLevel(*emptypb.Empty, grpc.ServerStreamingServer[LogLevelRequest]) error\n\t// A Tunnel represents one single connection where the client or\n\t// traffic-agent represents one end (the client-side) and the\n\t// traffic-manager represents the other (the server side). The first\n\t// message that a client sends when the tunnel is established is will\n\t// always contain the session ID, connection ID, and timeouts used by\n\t// the dialer endpoints.\n\tTunnel(grpc.BidiStreamingServer[TunnelMessage, TunnelMessage]) error\n\t// ReportMetrics is used by a traffic-agent to report metrics for streams\n\t// established when clients connect directly to traffic-agents using port-forward.\n\tReportMetrics(context.Context, *TunnelMetrics) (*emptypb.Empty, error)\n\t// UninstallAgents will uninstall the traffic-agent from the given workloads (or all\n\t// workloads if the list is empty).\n\tUninstallAgents(context.Context, *UninstallAgentsRequest) (*emptypb.Empty, error)\n\tmustEmbedUnimplementedManagerServer()\n}\n\n// UnimplementedManagerServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedManagerServer struct{}\n\nfunc (UnimplementedManagerServer) Version(context.Context, *emptypb.Empty) (*VersionInfo2, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Version not implemented\")\n}\nfunc (UnimplementedManagerServer) GetAgentImageFQN(context.Context, *emptypb.Empty) (*AgentImageFQN, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetAgentImageFQN not implemented\")\n}\nfunc (UnimplementedManagerServer) GetAgentConfig(context.Context, *AgentConfigRequest) (*AgentConfigResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetAgentConfig not implemented\")\n}\nfunc (UnimplementedManagerServer) GetClientConfig(context.Context, *emptypb.Empty) (*CLIConfig, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetClientConfig not implemented\")\n}\nfunc (UnimplementedManagerServer) GetTelepresenceAPI(context.Context, *emptypb.Empty) (*TelepresenceAPIInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetTelepresenceAPI not implemented\")\n}\nfunc (UnimplementedManagerServer) ArriveAsClient(context.Context, *ClientInfo) (*SessionInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ArriveAsClient not implemented\")\n}\nfunc (UnimplementedManagerServer) ReconnectAgent(context.Context, *ReconnectAgentRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ReconnectAgent not implemented\")\n}\nfunc (UnimplementedManagerServer) ReconnectClient(context.Context, *ReconnectClientRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ReconnectClient not implemented\")\n}\nfunc (UnimplementedManagerServer) ArriveAsAgent(context.Context, *AgentInfo) (*SessionInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ArriveAsAgent not implemented\")\n}\nfunc (UnimplementedManagerServer) Remain(context.Context, *RemainRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Remain not implemented\")\n}\nfunc (UnimplementedManagerServer) Depart(context.Context, *SessionInfo) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Depart not implemented\")\n}\nfunc (UnimplementedManagerServer) SetLogLevel(context.Context, *LogLevelRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method SetLogLevel not implemented\")\n}\nfunc (UnimplementedManagerServer) GetLogs(context.Context, *GetLogsRequest) (*LogsResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetLogs not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchAgentPods(*SessionInfo, grpc.ServerStreamingServer[AgentPodInfoSnapshot]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchAgentPods not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchAgentPodsDelta(*SessionInfo, grpc.ServerStreamingServer[AgentPodInfoDelta]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchAgentPodsDelta not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchAgents(*SessionInfo, grpc.ServerStreamingServer[AgentInfoSnapshot]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchAgents not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchAgentsDelta(*SessionInfo, grpc.ServerStreamingServer[AgentInfoDelta]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchAgentsDelta not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchIntercepts(*SessionInfo, grpc.ServerStreamingServer[InterceptInfoSnapshot]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchIntercepts not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchInterceptsDelta(*SessionInfo, grpc.ServerStreamingServer[InterceptInfoDelta]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchInterceptsDelta not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchWorkloads(*WorkloadEventsRequest, grpc.ServerStreamingServer[WorkloadEventsDelta]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchWorkloads not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchClusterInfo(*SessionInfo, grpc.ServerStreamingServer[ClusterInfo]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchClusterInfo not implemented\")\n}\nfunc (UnimplementedManagerServer) EnsureAgent(context.Context, *EnsureAgentRequest) (*AgentInfoSnapshot, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method EnsureAgent not implemented\")\n}\nfunc (UnimplementedManagerServer) PrepareIntercept(context.Context, *CreateInterceptRequest) (*PreparedIntercept, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method PrepareIntercept not implemented\")\n}\nfunc (UnimplementedManagerServer) CreateIntercept(context.Context, *CreateInterceptRequest) (*InterceptInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method CreateIntercept not implemented\")\n}\nfunc (UnimplementedManagerServer) RemoveIntercept(context.Context, *RemoveInterceptRequest2) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RemoveIntercept not implemented\")\n}\nfunc (UnimplementedManagerServer) GetIntercept(context.Context, *GetInterceptRequest) (*InterceptInfo, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetIntercept not implemented\")\n}\nfunc (UnimplementedManagerServer) ReviewIntercept(context.Context, *ReviewInterceptRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ReviewIntercept not implemented\")\n}\nfunc (UnimplementedManagerServer) GetKnownWorkloadKinds(context.Context, *SessionInfo) (*KnownWorkloadKinds, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method GetKnownWorkloadKinds not implemented\")\n}\nfunc (UnimplementedManagerServer) Lookup(context.Context, *LookupRequest) (*LookupResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Lookup not implemented\")\n}\nfunc (UnimplementedManagerServer) LookupDNS(context.Context, *DNSRequest) (*DNSResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method LookupDNS not implemented\")\n}\nfunc (UnimplementedManagerServer) WatchLogLevel(*emptypb.Empty, grpc.ServerStreamingServer[LogLevelRequest]) error {\n\treturn status.Error(codes.Unimplemented, \"method WatchLogLevel not implemented\")\n}\nfunc (UnimplementedManagerServer) Tunnel(grpc.BidiStreamingServer[TunnelMessage, TunnelMessage]) error {\n\treturn status.Error(codes.Unimplemented, \"method Tunnel not implemented\")\n}\nfunc (UnimplementedManagerServer) ReportMetrics(context.Context, *TunnelMetrics) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method ReportMetrics not implemented\")\n}\nfunc (UnimplementedManagerServer) UninstallAgents(context.Context, *UninstallAgentsRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method UninstallAgents not implemented\")\n}\nfunc (UnimplementedManagerServer) mustEmbedUnimplementedManagerServer() {}\nfunc (UnimplementedManagerServer) testEmbeddedByValue()                 {}\n\n// UnsafeManagerServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to ManagerServer will\n// result in compilation errors.\ntype UnsafeManagerServer interface {\n\tmustEmbedUnimplementedManagerServer()\n}\n\nfunc RegisterManagerServer(s grpc.ServiceRegistrar, srv ManagerServer) {\n\t// If the following call panics, it indicates UnimplementedManagerServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&Manager_ServiceDesc, srv)\n}\n\nfunc _Manager_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).Version(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_Version_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).Version(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_GetAgentImageFQN_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).GetAgentImageFQN(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_GetAgentImageFQN_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).GetAgentImageFQN(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_GetAgentConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AgentConfigRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).GetAgentConfig(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_GetAgentConfig_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).GetAgentConfig(ctx, req.(*AgentConfigRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_GetClientConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).GetClientConfig(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_GetClientConfig_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).GetClientConfig(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_GetTelepresenceAPI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(emptypb.Empty)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).GetTelepresenceAPI(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_GetTelepresenceAPI_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).GetTelepresenceAPI(ctx, req.(*emptypb.Empty))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_ArriveAsClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ClientInfo)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).ArriveAsClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_ArriveAsClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).ArriveAsClient(ctx, req.(*ClientInfo))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_ReconnectAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ReconnectAgentRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).ReconnectAgent(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_ReconnectAgent_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).ReconnectAgent(ctx, req.(*ReconnectAgentRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_ReconnectClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ReconnectClientRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).ReconnectClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_ReconnectClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).ReconnectClient(ctx, req.(*ReconnectClientRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_ArriveAsAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(AgentInfo)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).ArriveAsAgent(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_ArriveAsAgent_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).ArriveAsAgent(ctx, req.(*AgentInfo))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_Remain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RemainRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).Remain(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_Remain_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).Remain(ctx, req.(*RemainRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_Depart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SessionInfo)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).Depart(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_Depart_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).Depart(ctx, req.(*SessionInfo))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_SetLogLevel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LogLevelRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).SetLogLevel(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_SetLogLevel_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).SetLogLevel(ctx, req.(*LogLevelRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_GetLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetLogsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).GetLogs(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_GetLogs_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).GetLogs(ctx, req.(*GetLogsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_WatchAgentPods_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SessionInfo)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchAgentPods(m, &grpc.GenericServerStream[SessionInfo, AgentPodInfoSnapshot]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchAgentPodsServer = grpc.ServerStreamingServer[AgentPodInfoSnapshot]\n\nfunc _Manager_WatchAgentPodsDelta_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SessionInfo)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchAgentPodsDelta(m, &grpc.GenericServerStream[SessionInfo, AgentPodInfoDelta]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchAgentPodsDeltaServer = grpc.ServerStreamingServer[AgentPodInfoDelta]\n\nfunc _Manager_WatchAgents_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SessionInfo)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchAgents(m, &grpc.GenericServerStream[SessionInfo, AgentInfoSnapshot]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchAgentsServer = grpc.ServerStreamingServer[AgentInfoSnapshot]\n\nfunc _Manager_WatchAgentsDelta_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SessionInfo)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchAgentsDelta(m, &grpc.GenericServerStream[SessionInfo, AgentInfoDelta]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchAgentsDeltaServer = grpc.ServerStreamingServer[AgentInfoDelta]\n\nfunc _Manager_WatchIntercepts_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SessionInfo)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchIntercepts(m, &grpc.GenericServerStream[SessionInfo, InterceptInfoSnapshot]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchInterceptsServer = grpc.ServerStreamingServer[InterceptInfoSnapshot]\n\nfunc _Manager_WatchInterceptsDelta_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SessionInfo)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchInterceptsDelta(m, &grpc.GenericServerStream[SessionInfo, InterceptInfoDelta]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchInterceptsDeltaServer = grpc.ServerStreamingServer[InterceptInfoDelta]\n\nfunc _Manager_WatchWorkloads_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(WorkloadEventsRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchWorkloads(m, &grpc.GenericServerStream[WorkloadEventsRequest, WorkloadEventsDelta]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchWorkloadsServer = grpc.ServerStreamingServer[WorkloadEventsDelta]\n\nfunc _Manager_WatchClusterInfo_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(SessionInfo)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchClusterInfo(m, &grpc.GenericServerStream[SessionInfo, ClusterInfo]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchClusterInfoServer = grpc.ServerStreamingServer[ClusterInfo]\n\nfunc _Manager_EnsureAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EnsureAgentRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).EnsureAgent(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_EnsureAgent_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).EnsureAgent(ctx, req.(*EnsureAgentRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_PrepareIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateInterceptRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).PrepareIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_PrepareIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).PrepareIntercept(ctx, req.(*CreateInterceptRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_CreateIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateInterceptRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).CreateIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_CreateIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).CreateIntercept(ctx, req.(*CreateInterceptRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_RemoveIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RemoveInterceptRequest2)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).RemoveIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_RemoveIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).RemoveIntercept(ctx, req.(*RemoveInterceptRequest2))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_GetIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetInterceptRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).GetIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_GetIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).GetIntercept(ctx, req.(*GetInterceptRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_ReviewIntercept_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ReviewInterceptRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).ReviewIntercept(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_ReviewIntercept_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).ReviewIntercept(ctx, req.(*ReviewInterceptRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_GetKnownWorkloadKinds_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(SessionInfo)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).GetKnownWorkloadKinds(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_GetKnownWorkloadKinds_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).GetKnownWorkloadKinds(ctx, req.(*SessionInfo))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_Lookup_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(LookupRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).Lookup(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_Lookup_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).Lookup(ctx, req.(*LookupRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_LookupDNS_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DNSRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).LookupDNS(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_LookupDNS_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).LookupDNS(ctx, req.(*DNSRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_WatchLogLevel_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(emptypb.Empty)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(ManagerServer).WatchLogLevel(m, &grpc.GenericServerStream[emptypb.Empty, LogLevelRequest]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_WatchLogLevelServer = grpc.ServerStreamingServer[LogLevelRequest]\n\nfunc _Manager_Tunnel_Handler(srv interface{}, stream grpc.ServerStream) error {\n\treturn srv.(ManagerServer).Tunnel(&grpc.GenericServerStream[TunnelMessage, TunnelMessage]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Manager_TunnelServer = grpc.BidiStreamingServer[TunnelMessage, TunnelMessage]\n\nfunc _Manager_ReportMetrics_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(TunnelMetrics)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).ReportMetrics(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_ReportMetrics_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).ReportMetrics(ctx, req.(*TunnelMetrics))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Manager_UninstallAgents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UninstallAgentsRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(ManagerServer).UninstallAgents(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Manager_UninstallAgents_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(ManagerServer).UninstallAgents(ctx, req.(*UninstallAgentsRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// Manager_ServiceDesc is the grpc.ServiceDesc for Manager service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar Manager_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"telepresence.manager.Manager\",\n\tHandlerType: (*ManagerServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"Version\",\n\t\t\tHandler:    _Manager_Version_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetAgentImageFQN\",\n\t\t\tHandler:    _Manager_GetAgentImageFQN_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetAgentConfig\",\n\t\t\tHandler:    _Manager_GetAgentConfig_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetClientConfig\",\n\t\t\tHandler:    _Manager_GetClientConfig_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetTelepresenceAPI\",\n\t\t\tHandler:    _Manager_GetTelepresenceAPI_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ArriveAsClient\",\n\t\t\tHandler:    _Manager_ArriveAsClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ReconnectAgent\",\n\t\t\tHandler:    _Manager_ReconnectAgent_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ReconnectClient\",\n\t\t\tHandler:    _Manager_ReconnectClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ArriveAsAgent\",\n\t\t\tHandler:    _Manager_ArriveAsAgent_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Remain\",\n\t\t\tHandler:    _Manager_Remain_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Depart\",\n\t\t\tHandler:    _Manager_Depart_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"SetLogLevel\",\n\t\t\tHandler:    _Manager_SetLogLevel_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetLogs\",\n\t\t\tHandler:    _Manager_GetLogs_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"EnsureAgent\",\n\t\t\tHandler:    _Manager_EnsureAgent_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"PrepareIntercept\",\n\t\t\tHandler:    _Manager_PrepareIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateIntercept\",\n\t\t\tHandler:    _Manager_CreateIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoveIntercept\",\n\t\t\tHandler:    _Manager_RemoveIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetIntercept\",\n\t\t\tHandler:    _Manager_GetIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ReviewIntercept\",\n\t\t\tHandler:    _Manager_ReviewIntercept_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetKnownWorkloadKinds\",\n\t\t\tHandler:    _Manager_GetKnownWorkloadKinds_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Lookup\",\n\t\t\tHandler:    _Manager_Lookup_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"LookupDNS\",\n\t\t\tHandler:    _Manager_LookupDNS_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ReportMetrics\",\n\t\t\tHandler:    _Manager_ReportMetrics_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UninstallAgents\",\n\t\t\tHandler:    _Manager_UninstallAgents_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"WatchAgentPods\",\n\t\t\tHandler:       _Manager_WatchAgentPods_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchAgentPodsDelta\",\n\t\t\tHandler:       _Manager_WatchAgentPodsDelta_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchAgents\",\n\t\t\tHandler:       _Manager_WatchAgents_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchAgentsDelta\",\n\t\t\tHandler:       _Manager_WatchAgentsDelta_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchIntercepts\",\n\t\t\tHandler:       _Manager_WatchIntercepts_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchInterceptsDelta\",\n\t\t\tHandler:       _Manager_WatchInterceptsDelta_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchWorkloads\",\n\t\t\tHandler:       _Manager_WatchWorkloads_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchClusterInfo\",\n\t\t\tHandler:       _Manager_WatchClusterInfo_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"WatchLogLevel\",\n\t\t\tHandler:       _Manager_WatchLogLevel_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t\t{\n\t\t\tStreamName:    \"Tunnel\",\n\t\t\tHandler:       _Manager_Tunnel_Handler,\n\t\t\tServerStreams: true,\n\t\t\tClientStreams: true,\n\t\t},\n\t},\n\tMetadata: \"manager/manager.proto\",\n}\n"
  },
  {
    "path": "rpc/teleroute/service.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.11\n// \tprotoc        v6.30.2\n// source: teleroute/service.proto\n\npackage teleroute\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\ntype ConnectRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Gateways sent to CreateNetwork in netip.Prefix binary form\n\tGateways [][]byte `protobuf:\"bytes,1,rep,name=gateways,proto3\" json:\"gateways,omitempty\"`\n\t// The host pid of the connecting plugin\n\tPid           int64 `protobuf:\"varint,2,opt,name=pid,proto3\" json:\"pid,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ConnectRequest) Reset() {\n\t*x = ConnectRequest{}\n\tmi := &file_teleroute_service_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ConnectRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ConnectRequest) ProtoMessage() {}\n\nfunc (x *ConnectRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_teleroute_service_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ConnectRequest.ProtoReflect.Descriptor instead.\nfunc (*ConnectRequest) Descriptor() ([]byte, []int) {\n\treturn file_teleroute_service_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *ConnectRequest) GetGateways() [][]byte {\n\tif x != nil {\n\t\treturn x.Gateways\n\t}\n\treturn nil\n}\n\nfunc (x *ConnectRequest) GetPid() int64 {\n\tif x != nil {\n\t\treturn x.Pid\n\t}\n\treturn 0\n}\n\ntype CreateEndpointRequest struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The id of this endpoint.\n\tId string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\t// The joining container's IPv4 address in netip.Addr binary form\n\tAddrIpv4 []byte `protobuf:\"bytes,2,opt,name=addr_ipv4,json=addrIpv4,proto3\" json:\"addr_ipv4,omitempty\"`\n\t// The joining container's IPv6 address in netip.Addr binary form\n\tAddrIpv6 []byte `protobuf:\"bytes,4,opt,name=addr_ipv6,json=addrIpv6,proto3\" json:\"addr_ipv6,omitempty\"`\n\t// Set to true if this is the daemon endpoint\n\tDaemon        bool `protobuf:\"varint,3,opt,name=daemon,proto3\" json:\"daemon,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreateEndpointRequest) Reset() {\n\t*x = CreateEndpointRequest{}\n\tmi := &file_teleroute_service_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateEndpointRequest) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateEndpointRequest) ProtoMessage() {}\n\nfunc (x *CreateEndpointRequest) ProtoReflect() protoreflect.Message {\n\tmi := &file_teleroute_service_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateEndpointRequest.ProtoReflect.Descriptor instead.\nfunc (*CreateEndpointRequest) Descriptor() ([]byte, []int) {\n\treturn file_teleroute_service_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *CreateEndpointRequest) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *CreateEndpointRequest) GetAddrIpv4() []byte {\n\tif x != nil {\n\t\treturn x.AddrIpv4\n\t}\n\treturn nil\n}\n\nfunc (x *CreateEndpointRequest) GetAddrIpv6() []byte {\n\tif x != nil {\n\t\treturn x.AddrIpv6\n\t}\n\treturn nil\n}\n\nfunc (x *CreateEndpointRequest) GetDaemon() bool {\n\tif x != nil {\n\t\treturn x.Daemon\n\t}\n\treturn false\n}\n\ntype EndpointIdentifier struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The endpoint ID.\n\tId            string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *EndpointIdentifier) Reset() {\n\t*x = EndpointIdentifier{}\n\tmi := &file_teleroute_service_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *EndpointIdentifier) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*EndpointIdentifier) ProtoMessage() {}\n\nfunc (x *EndpointIdentifier) ProtoReflect() protoreflect.Message {\n\tmi := &file_teleroute_service_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use EndpointIdentifier.ProtoReflect.Descriptor instead.\nfunc (*EndpointIdentifier) Descriptor() ([]byte, []int) {\n\treturn file_teleroute_service_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *EndpointIdentifier) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\ntype JoinResponse struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The name of the container interface of the veth-pair.\n\tInterfaceSrcName string `protobuf:\"bytes,1,opt,name=interface_src_name,json=interfaceSrcName,proto3\" json:\"interface_src_name,omitempty\"`\n\t// The prefix to use when the container interface is renamed.\n\tInterfaceDstPrefix string   `protobuf:\"bytes,2,opt,name=interface_dst_prefix,json=interfaceDstPrefix,proto3\" json:\"interface_dst_prefix,omitempty\"`\n\tRoutes             [][]byte `protobuf:\"bytes,3,rep,name=routes,proto3\" json:\"routes,omitempty\"`\n\tVia                []byte   `protobuf:\"bytes,4,opt,name=via,proto3\" json:\"via,omitempty\"`\n\tGwIpV4             []byte   `protobuf:\"bytes,5,opt,name=gw_ip_v4,json=gwIpV4,proto3\" json:\"gw_ip_v4,omitempty\"`\n\tGwIpV6             []byte   `protobuf:\"bytes,6,opt,name=gw_ip_v6,json=gwIpV6,proto3\" json:\"gw_ip_v6,omitempty\"`\n\tDisableGw          bool     `protobuf:\"varint,7,opt,name=disable_gw,json=disableGw,proto3\" json:\"disable_gw,omitempty\"`\n\tunknownFields      protoimpl.UnknownFields\n\tsizeCache          protoimpl.SizeCache\n}\n\nfunc (x *JoinResponse) Reset() {\n\t*x = JoinResponse{}\n\tmi := &file_teleroute_service_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *JoinResponse) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*JoinResponse) ProtoMessage() {}\n\nfunc (x *JoinResponse) ProtoReflect() protoreflect.Message {\n\tmi := &file_teleroute_service_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use JoinResponse.ProtoReflect.Descriptor instead.\nfunc (*JoinResponse) Descriptor() ([]byte, []int) {\n\treturn file_teleroute_service_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *JoinResponse) GetInterfaceSrcName() string {\n\tif x != nil {\n\t\treturn x.InterfaceSrcName\n\t}\n\treturn \"\"\n}\n\nfunc (x *JoinResponse) GetInterfaceDstPrefix() string {\n\tif x != nil {\n\t\treturn x.InterfaceDstPrefix\n\t}\n\treturn \"\"\n}\n\nfunc (x *JoinResponse) GetRoutes() [][]byte {\n\tif x != nil {\n\t\treturn x.Routes\n\t}\n\treturn nil\n}\n\nfunc (x *JoinResponse) GetVia() []byte {\n\tif x != nil {\n\t\treturn x.Via\n\t}\n\treturn nil\n}\n\nfunc (x *JoinResponse) GetGwIpV4() []byte {\n\tif x != nil {\n\t\treturn x.GwIpV4\n\t}\n\treturn nil\n}\n\nfunc (x *JoinResponse) GetGwIpV6() []byte {\n\tif x != nil {\n\t\treturn x.GwIpV6\n\t}\n\treturn nil\n}\n\nfunc (x *JoinResponse) GetDisableGw() bool {\n\tif x != nil {\n\t\treturn x.DisableGw\n\t}\n\treturn false\n}\n\ntype Info struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tInfo          map[string]string      `protobuf:\"bytes,1,rep,name=info,proto3\" json:\"info,omitempty\" protobuf_key:\"bytes,1,opt,name=key\" protobuf_val:\"bytes,2,opt,name=value\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Info) Reset() {\n\t*x = Info{}\n\tmi := &file_teleroute_service_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Info) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Info) ProtoMessage() {}\n\nfunc (x *Info) ProtoReflect() protoreflect.Message {\n\tmi := &file_teleroute_service_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Info.ProtoReflect.Descriptor instead.\nfunc (*Info) Descriptor() ([]byte, []int) {\n\treturn file_teleroute_service_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *Info) GetInfo() map[string]string {\n\tif x != nil {\n\t\treturn x.Info\n\t}\n\treturn nil\n}\n\nvar File_teleroute_service_proto protoreflect.FileDescriptor\n\nconst file_teleroute_service_proto_rawDesc = \"\" +\n\t\"\\n\" +\n\t\"\\x17teleroute/service.proto\\x12\\x16telepresence.teleroute\\x1a\\x1bgoogle/protobuf/empty.proto\\\">\\n\" +\n\t\"\\x0eConnectRequest\\x12\\x1a\\n\" +\n\t\"\\bgateways\\x18\\x01 \\x03(\\fR\\bgateways\\x12\\x10\\n\" +\n\t\"\\x03pid\\x18\\x02 \\x01(\\x03R\\x03pid\\\"y\\n\" +\n\t\"\\x15CreateEndpointRequest\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\tR\\x02id\\x12\\x1b\\n\" +\n\t\"\\taddr_ipv4\\x18\\x02 \\x01(\\fR\\baddrIpv4\\x12\\x1b\\n\" +\n\t\"\\taddr_ipv6\\x18\\x04 \\x01(\\fR\\baddrIpv6\\x12\\x16\\n\" +\n\t\"\\x06daemon\\x18\\x03 \\x01(\\bR\\x06daemon\\\"$\\n\" +\n\t\"\\x12EndpointIdentifier\\x12\\x0e\\n\" +\n\t\"\\x02id\\x18\\x01 \\x01(\\tR\\x02id\\\"\\xeb\\x01\\n\" +\n\t\"\\fJoinResponse\\x12,\\n\" +\n\t\"\\x12interface_src_name\\x18\\x01 \\x01(\\tR\\x10interfaceSrcName\\x120\\n\" +\n\t\"\\x14interface_dst_prefix\\x18\\x02 \\x01(\\tR\\x12interfaceDstPrefix\\x12\\x16\\n\" +\n\t\"\\x06routes\\x18\\x03 \\x03(\\fR\\x06routes\\x12\\x10\\n\" +\n\t\"\\x03via\\x18\\x04 \\x01(\\fR\\x03via\\x12\\x18\\n\" +\n\t\"\\bgw_ip_v4\\x18\\x05 \\x01(\\fR\\x06gwIpV4\\x12\\x18\\n\" +\n\t\"\\bgw_ip_v6\\x18\\x06 \\x01(\\fR\\x06gwIpV6\\x12\\x1d\\n\" +\n\t\"\\n\" +\n\t\"disable_gw\\x18\\a \\x01(\\bR\\tdisableGw\\\"{\\n\" +\n\t\"\\x04Info\\x12:\\n\" +\n\t\"\\x04info\\x18\\x01 \\x03(\\v2&.telepresence.teleroute.Info.InfoEntryR\\x04info\\x1a7\\n\" +\n\t\"\\tInfoEntry\\x12\\x10\\n\" +\n\t\"\\x03key\\x18\\x01 \\x01(\\tR\\x03key\\x12\\x14\\n\" +\n\t\"\\x05value\\x18\\x02 \\x01(\\tR\\x05value:\\x028\\x012\\xb4\\x03\\n\" +\n\t\"\\tTeleroute\\x12Q\\n\" +\n\t\"\\aConnect\\x12&.telepresence.teleroute.ConnectRequest\\x1a\\x1c.telepresence.teleroute.Info0\\x01\\x12W\\n\" +\n\t\"\\x0eCreateEndpoint\\x12-.telepresence.teleroute.CreateEndpointRequest\\x1a\\x16.google.protobuf.Empty\\x12T\\n\" +\n\t\"\\x0eRemoveEndpoint\\x12*.telepresence.teleroute.EndpointIdentifier\\x1a\\x16.google.protobuf.Empty\\x12X\\n\" +\n\t\"\\x04Join\\x12*.telepresence.teleroute.EndpointIdentifier\\x1a$.telepresence.teleroute.JoinResponse\\x12K\\n\" +\n\t\"\\x05Leave\\x12*.telepresence.teleroute.EndpointIdentifier\\x1a\\x16.google.protobuf.EmptyB9Z7github.com/telepresenceio/telepresence/rpc/v2/telerouteb\\x06proto3\"\n\nvar (\n\tfile_teleroute_service_proto_rawDescOnce sync.Once\n\tfile_teleroute_service_proto_rawDescData []byte\n)\n\nfunc file_teleroute_service_proto_rawDescGZIP() []byte {\n\tfile_teleroute_service_proto_rawDescOnce.Do(func() {\n\t\tfile_teleroute_service_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_teleroute_service_proto_rawDesc), len(file_teleroute_service_proto_rawDesc)))\n\t})\n\treturn file_teleroute_service_proto_rawDescData\n}\n\nvar file_teleroute_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6)\nvar file_teleroute_service_proto_goTypes = []any{\n\t(*ConnectRequest)(nil),        // 0: telepresence.teleroute.ConnectRequest\n\t(*CreateEndpointRequest)(nil), // 1: telepresence.teleroute.CreateEndpointRequest\n\t(*EndpointIdentifier)(nil),    // 2: telepresence.teleroute.EndpointIdentifier\n\t(*JoinResponse)(nil),          // 3: telepresence.teleroute.JoinResponse\n\t(*Info)(nil),                  // 4: telepresence.teleroute.Info\n\tnil,                           // 5: telepresence.teleroute.Info.InfoEntry\n\t(*emptypb.Empty)(nil),         // 6: google.protobuf.Empty\n}\nvar file_teleroute_service_proto_depIdxs = []int32{\n\t5, // 0: telepresence.teleroute.Info.info:type_name -> telepresence.teleroute.Info.InfoEntry\n\t0, // 1: telepresence.teleroute.Teleroute.Connect:input_type -> telepresence.teleroute.ConnectRequest\n\t1, // 2: telepresence.teleroute.Teleroute.CreateEndpoint:input_type -> telepresence.teleroute.CreateEndpointRequest\n\t2, // 3: telepresence.teleroute.Teleroute.RemoveEndpoint:input_type -> telepresence.teleroute.EndpointIdentifier\n\t2, // 4: telepresence.teleroute.Teleroute.Join:input_type -> telepresence.teleroute.EndpointIdentifier\n\t2, // 5: telepresence.teleroute.Teleroute.Leave:input_type -> telepresence.teleroute.EndpointIdentifier\n\t4, // 6: telepresence.teleroute.Teleroute.Connect:output_type -> telepresence.teleroute.Info\n\t6, // 7: telepresence.teleroute.Teleroute.CreateEndpoint:output_type -> google.protobuf.Empty\n\t6, // 8: telepresence.teleroute.Teleroute.RemoveEndpoint:output_type -> google.protobuf.Empty\n\t3, // 9: telepresence.teleroute.Teleroute.Join:output_type -> telepresence.teleroute.JoinResponse\n\t6, // 10: telepresence.teleroute.Teleroute.Leave:output_type -> google.protobuf.Empty\n\t6, // [6:11] is the sub-list for method output_type\n\t1, // [1:6] is the sub-list for method input_type\n\t1, // [1:1] is the sub-list for extension type_name\n\t1, // [1:1] is the sub-list for extension extendee\n\t0, // [0:1] is the sub-list for field type_name\n}\n\nfunc init() { file_teleroute_service_proto_init() }\nfunc file_teleroute_service_proto_init() {\n\tif File_teleroute_service_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_teleroute_service_proto_rawDesc), len(file_teleroute_service_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   6,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_teleroute_service_proto_goTypes,\n\t\tDependencyIndexes: file_teleroute_service_proto_depIdxs,\n\t\tMessageInfos:      file_teleroute_service_proto_msgTypes,\n\t}.Build()\n\tFile_teleroute_service_proto = out.File\n\tfile_teleroute_service_proto_goTypes = nil\n\tfile_teleroute_service_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "rpc/teleroute/service.proto",
    "content": "syntax = \"proto3\";\npackage telepresence.teleroute;\nimport \"google/protobuf/empty.proto\";\n\noption go_package = \"github.com/telepresenceio/telepresence/rpc/v2/teleroute\";\n\nservice Teleroute {\n  rpc Connect(ConnectRequest) returns (stream Info);\n\n  rpc CreateEndpoint(CreateEndpointRequest) returns (google.protobuf.Empty);\n\n  rpc RemoveEndpoint(EndpointIdentifier) returns (google.protobuf.Empty);\n\n  rpc Join(EndpointIdentifier) returns (JoinResponse);\n\n  rpc Leave(EndpointIdentifier) returns (google.protobuf.Empty);\n}\n\nmessage ConnectRequest {\n  // Gateways sent to CreateNetwork in netip.Prefix binary form\n  repeated bytes gateways = 1;\n\n  // The host pid of the connecting plugin\n  int64 pid = 2;\n}\n\nmessage CreateEndpointRequest {\n  // The id of this endpoint.\n  string id = 1;\n\n  // The joining container's IPv4 address in netip.Addr binary form\n  bytes addr_ipv4 = 2;\n\n  // The joining container's IPv6 address in netip.Addr binary form\n  bytes addr_ipv6 = 4;\n\n  // Set to true if this is the daemon endpoint\n  bool daemon = 3;\n}\n\nmessage EndpointIdentifier {\n  // The endpoint ID.\n  string id = 1;\n}\n\nmessage JoinResponse {\n  // The name of the container interface of the veth-pair.\n  string interface_src_name = 1;\n\n  // The prefix to use when the container interface is renamed.\n  string interface_dst_prefix = 2;\n\n  repeated bytes routes = 3;\n\n  bytes via = 4;\n\n  bytes gw_ip_v4 = 5;\n\n  bytes gw_ip_v6 = 6;\n\n  bool disable_gw = 7;\n}\n\nmessage Info {\n  map<string,string> info = 1;\n}"
  },
  {
    "path": "rpc/teleroute/service_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.6.1\n// - protoc             v6.30.2\n// source: teleroute/service.proto\n\npackage teleroute\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n\temptypb \"google.golang.org/protobuf/types/known/emptypb\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tTeleroute_Connect_FullMethodName        = \"/telepresence.teleroute.Teleroute/Connect\"\n\tTeleroute_CreateEndpoint_FullMethodName = \"/telepresence.teleroute.Teleroute/CreateEndpoint\"\n\tTeleroute_RemoveEndpoint_FullMethodName = \"/telepresence.teleroute.Teleroute/RemoveEndpoint\"\n\tTeleroute_Join_FullMethodName           = \"/telepresence.teleroute.Teleroute/Join\"\n\tTeleroute_Leave_FullMethodName          = \"/telepresence.teleroute.Teleroute/Leave\"\n)\n\n// TelerouteClient is the client API for Teleroute service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\ntype TelerouteClient interface {\n\tConnect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Info], error)\n\tCreateEndpoint(ctx context.Context, in *CreateEndpointRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tRemoveEndpoint(ctx context.Context, in *EndpointIdentifier, opts ...grpc.CallOption) (*emptypb.Empty, error)\n\tJoin(ctx context.Context, in *EndpointIdentifier, opts ...grpc.CallOption) (*JoinResponse, error)\n\tLeave(ctx context.Context, in *EndpointIdentifier, opts ...grpc.CallOption) (*emptypb.Empty, error)\n}\n\ntype telerouteClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewTelerouteClient(cc grpc.ClientConnInterface) TelerouteClient {\n\treturn &telerouteClient{cc}\n}\n\nfunc (c *telerouteClient) Connect(ctx context.Context, in *ConnectRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[Info], error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tstream, err := c.cc.NewStream(ctx, &Teleroute_ServiceDesc.Streams[0], Teleroute_Connect_FullMethodName, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tx := &grpc.GenericClientStream[ConnectRequest, Info]{ClientStream: stream}\n\tif err := x.ClientStream.SendMsg(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := x.ClientStream.CloseSend(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn x, nil\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Teleroute_ConnectClient = grpc.ServerStreamingClient[Info]\n\nfunc (c *telerouteClient) CreateEndpoint(ctx context.Context, in *CreateEndpointRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Teleroute_CreateEndpoint_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *telerouteClient) RemoveEndpoint(ctx context.Context, in *EndpointIdentifier, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Teleroute_RemoveEndpoint_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *telerouteClient) Join(ctx context.Context, in *EndpointIdentifier, opts ...grpc.CallOption) (*JoinResponse, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(JoinResponse)\n\terr := c.cc.Invoke(ctx, Teleroute_Join_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *telerouteClient) Leave(ctx context.Context, in *EndpointIdentifier, opts ...grpc.CallOption) (*emptypb.Empty, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(emptypb.Empty)\n\terr := c.cc.Invoke(ctx, Teleroute_Leave_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// TelerouteServer is the server API for Teleroute service.\n// All implementations must embed UnimplementedTelerouteServer\n// for forward compatibility.\ntype TelerouteServer interface {\n\tConnect(*ConnectRequest, grpc.ServerStreamingServer[Info]) error\n\tCreateEndpoint(context.Context, *CreateEndpointRequest) (*emptypb.Empty, error)\n\tRemoveEndpoint(context.Context, *EndpointIdentifier) (*emptypb.Empty, error)\n\tJoin(context.Context, *EndpointIdentifier) (*JoinResponse, error)\n\tLeave(context.Context, *EndpointIdentifier) (*emptypb.Empty, error)\n\tmustEmbedUnimplementedTelerouteServer()\n}\n\n// UnimplementedTelerouteServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedTelerouteServer struct{}\n\nfunc (UnimplementedTelerouteServer) Connect(*ConnectRequest, grpc.ServerStreamingServer[Info]) error {\n\treturn status.Error(codes.Unimplemented, \"method Connect not implemented\")\n}\nfunc (UnimplementedTelerouteServer) CreateEndpoint(context.Context, *CreateEndpointRequest) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method CreateEndpoint not implemented\")\n}\nfunc (UnimplementedTelerouteServer) RemoveEndpoint(context.Context, *EndpointIdentifier) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method RemoveEndpoint not implemented\")\n}\nfunc (UnimplementedTelerouteServer) Join(context.Context, *EndpointIdentifier) (*JoinResponse, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Join not implemented\")\n}\nfunc (UnimplementedTelerouteServer) Leave(context.Context, *EndpointIdentifier) (*emptypb.Empty, error) {\n\treturn nil, status.Error(codes.Unimplemented, \"method Leave not implemented\")\n}\nfunc (UnimplementedTelerouteServer) mustEmbedUnimplementedTelerouteServer() {}\nfunc (UnimplementedTelerouteServer) testEmbeddedByValue()                   {}\n\n// UnsafeTelerouteServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to TelerouteServer will\n// result in compilation errors.\ntype UnsafeTelerouteServer interface {\n\tmustEmbedUnimplementedTelerouteServer()\n}\n\nfunc RegisterTelerouteServer(s grpc.ServiceRegistrar, srv TelerouteServer) {\n\t// If the following call panics, it indicates UnimplementedTelerouteServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&Teleroute_ServiceDesc, srv)\n}\n\nfunc _Teleroute_Connect_Handler(srv interface{}, stream grpc.ServerStream) error {\n\tm := new(ConnectRequest)\n\tif err := stream.RecvMsg(m); err != nil {\n\t\treturn err\n\t}\n\treturn srv.(TelerouteServer).Connect(m, &grpc.GenericServerStream[ConnectRequest, Info]{ServerStream: stream})\n}\n\n// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.\ntype Teleroute_ConnectServer = grpc.ServerStreamingServer[Info]\n\nfunc _Teleroute_CreateEndpoint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateEndpointRequest)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(TelerouteServer).CreateEndpoint(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Teleroute_CreateEndpoint_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(TelerouteServer).CreateEndpoint(ctx, req.(*CreateEndpointRequest))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Teleroute_RemoveEndpoint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EndpointIdentifier)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(TelerouteServer).RemoveEndpoint(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Teleroute_RemoveEndpoint_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(TelerouteServer).RemoveEndpoint(ctx, req.(*EndpointIdentifier))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Teleroute_Join_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EndpointIdentifier)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(TelerouteServer).Join(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Teleroute_Join_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(TelerouteServer).Join(ctx, req.(*EndpointIdentifier))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Teleroute_Leave_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(EndpointIdentifier)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(TelerouteServer).Leave(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Teleroute_Leave_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(TelerouteServer).Leave(ctx, req.(*EndpointIdentifier))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// Teleroute_ServiceDesc is the grpc.ServiceDesc for Teleroute service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar Teleroute_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"telepresence.teleroute.Teleroute\",\n\tHandlerType: (*TelerouteServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"CreateEndpoint\",\n\t\t\tHandler:    _Teleroute_CreateEndpoint_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RemoveEndpoint\",\n\t\t\tHandler:    _Teleroute_RemoveEndpoint_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Join\",\n\t\t\tHandler:    _Teleroute_Join_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"Leave\",\n\t\t\tHandler:    _Teleroute_Leave_Handler,\n\t\t},\n\t},\n\tStreams: []grpc.StreamDesc{\n\t\t{\n\t\t\tStreamName:    \"Connect\",\n\t\t\tHandler:       _Teleroute_Connect_Handler,\n\t\t\tServerStreams: true,\n\t\t},\n\t},\n\tMetadata: \"teleroute/service.proto\",\n}\n"
  },
  {
    "path": "test-infra/aws-okd/README.md",
    "content": "# Setting up an openshift environment to test\n\nThe resources in this folder should help you set up an openshift environment on AWS.\nYou can use this to test the compatibility of Telepresence within openshift.\n\n## 0. Prerequisites\n\n* A route53 zone in your AWS account. A hosted zone will be created as a subdomain of this existing zone to serve as the DNS name for the VPN's certificates.\n* A configured, logged-in AWS CLI\n* `terraform` must be installed, then you'll need to run `terraform init` in the `dns` directory\n* An account on [RedHat's portal](https://console.redhat.com/)\n\n## 1. Setting up DNS\n\nA DNS hosted zone needs to be created for the cluster to be accessible.\nIt is suggested that you create this as a subdomain of an already existing zone for a domain that you own.\n\nTo do this, simply cd into the `dns` directory, and create a `terraform.tfvars` file like the following:\n\n```hcl\nparent_domain           = \"foo.net\" # The name of an existing route 53 hosted zone\nchild_subdomain         = \"child\" # The name of the subdomain -- a zone \"child.foo.net\" will be created.\nchild_subdomain_comment = \"My DNS zone for openshift\" # A human readable comment for the hosted zone\naws_region              = \"us-west-2\" # The AWS region to create the hosted zone in\n```\n\n## 2. Create an ssh keypair for openshift\n\nYou'll need an ssh private/public key pair to login to your openshift nodes.\nTo do this, simply:\n\n```bash\nssh-keygen -t ed25519 -N '' -f ~/.ssh/openshift\n```\n\nThen, set up an ssh agent and add the key to it:\n\n```bash\neval `ssh-agent -s`\nssh-add ~/.ssh/openshift\n```\n\n## 3. Download openshift installer\n\nDownload an openshift installer from [this page](https://github.com/openshift/okd/releases).\nIts name will look like `openshift-install-mac-4.8.0-0.okd-2021-11-14-052418.tar.gz` (with differences for version and OS).\nExtract the installer somewhere on your computer.\n\n## 4. Run the Openshift installer\n\nAt this point all that's left to do is to launch the installer:\n\n```bash\n./openshift-install create cluster --dir=./tele-test --log-level=info\n```\n\nThis installer will ask you a number of questions, starting with asking you to select an SSH key.\nSimply select the one you created in step 2:\n\n```\n? SSH Public Key  [Use arrows to move, type to filter, ? for more help]\n  /Users/USERNAME/.ssh/id_rsa.pub\n> /Users/USERNAME/.ssh/openshift.pub\n  <none>\n```\n\nYou'll then have to select `aws` as the platform:\n\n```\n? Platform  [Use arrows to move, type to filter, ? for more help]\n> aws\n  azure\n  gcp\n  openstack\n  ovirt\n  vsphere\n```\n\nThen select the AWS region from step 1:\n\n```\n? Region  [Use arrows to move, type to filter, ? for more help]\n  eu-west-3 (Europe (Paris))\n  me-south-1 (Middle East (Bahrain))\n  sa-east-1 (South America (Sao Paulo))\n  us-east-1 (US East (N. Virginia))\n  us-east-2 (US East (Ohio))\n  us-west-1 (US West (N. California))\n> us-west-2 (US West (Oregon))\n```\n\nThe installer will next ask you for a domain -- find the domain from step 1:\n\n```\n? Base Domain  [Use arrows to move, type to filter, ? for more help]\n  bar.org\n  abc.foo.net\n  xyz.foo.net\n> child.foo.net\n  foo.net\n  bar.foo.net\n  etc.foo.net\n```\n\nThen the name of the cluster:\n\n```\n? Cluster Name [? for help] my-test-okd\n```\n\nAnd finally a pull secret; to get this pull secret, login to [https://console.redhat.com/openshift/install/pull-secret](https://console.redhat.com/openshift/install/pull-secret):\n\n```\n? Pull Secret **************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************\n```\n\nAt that point, the cluster will be created.\nThis may take slightly longer than an hour. At the end, the installer will prompt you to update your kubeconfig:\n\n```\nINFO Install complete!\nINFO To access the cluster as the system:admin user when using 'oc', run 'export KUBECONFIG=/Users/USER/openshift/tele-test/auth/kubeconfig'\nINFO Access the OpenShift web-console here: https://console-openshift-console.apps.my-test-okd.child.foo.net\nINFO Login to the console with user: \"kubeadmin\", and password: \"XXXXX-XXXXX-XXXXX-XXXXX\"\n```\n\nOnce you've `export`ed your kubeconfig, you'll have a usable openshift cluster!\n\n## 5. Install Telepresence\n\nInstalling Telepresence on openshift requires some special configuration.\n\nThe easiest way to do this is to install through the Helm chart, from\nthe root of your telepresence.git checkout (`../../` from this\ndirectory), run:\n\n```bash\nmkdir tmpdir\ngo run ./packaging/gen_chart.go tmpdir\nhelm install traffic-manager ./tmpdir/telepresence-*.tgz -n ambassador --create-namespace --set securityContext=null\n```\n\nAt that point, `telepresence connect` should work, and you can start doing testing!\n\n## 6. Destroy the cluster\n\nYou probably don't want the cluster to hang around forever if you're just using it to test Telepresence.\nTo destroy it, simply run:\n\n```bash\n./openshift-install destroy cluster --dir=./tele-test --log-level=info\n```\n"
  },
  {
    "path": "test-infra/aws-okd/dns/.gitignore",
    "content": ".terraform\n.terraform*\nterraform.tfvars\nterraform.tfstate\n"
  },
  {
    "path": "test-infra/aws-okd/dns/dns.tf",
    "content": "variable \"parent_domain\" {\n  type        = string\n  description = \"The name of the parent domain\"\n}\n\nvariable \"child_subdomain\" {\n  type        = string\n  description = \"The name of the child domain\"\n}\n\nvariable \"child_subdomain_comment\" {\n  type        = string\n  description = \"The comment of the child domain\"\n}\n\nvariable \"aws_region\" {\n  type        = string\n  description = \"The region to create the resources in\"\n}\n\nterraform {\n  required_providers {\n    aws = {\n      source  = \"hashicorp/aws\",\n      version = \"~> 3.20\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = var.aws_region\n}\n\ndata \"aws_route53_zone\" \"parent_dns_zone\" {\n  name = var.parent_domain\n}\n\n\nresource \"aws_route53_zone\" \"child_dns_zone\" {\n  name    = \"${var.child_subdomain}.${data.aws_route53_zone.parent_dns_zone.name}\"\n  comment = var.child_subdomain_comment\n}\n\nresource \"aws_route53_record\" \"child_dns_route\" {\n  zone_id = data.aws_route53_zone.parent_dns_zone.id\n  name    = var.child_subdomain\n  type    = \"NS\"\n  ttl     = 3600\n  records = aws_route53_zone.child_dns_zone.name_servers\n}\n"
  },
  {
    "path": "test-infra/aws-vpn/.gitignore",
    "content": "certs/*\n*.tfstate\n*.backup\n*.tfvars\n.terraform\n.terraform.*\n"
  },
  {
    "path": "test-infra/aws-vpn/README.md",
    "content": "# aws-vpn infrastructure example\n\nThis example will provision a VPC on AWS, together with an EKS cluster that's private to the VPC and a ClientVPN endpoint to access it.\nThis is basically all that is needed for a private EKS cluster inside a VPC, and can be used to test how telepresence interacts with different VPN scenarios.\n\n## How to use it\n\n### 0. Prerequisites\n\nYou will need a route53 zone in your AWS account.\nA hosted zone will be created as a subdomain of this existing zone to serve as the DNS name for the VPN's certificates.\n\nYou'll also need to configure your `aws` CLI and authenticate into AWS. Please read the [AWS docs](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html) to know how to do this.\n\nFinally, you need to install [terraform](https://www.terraform.io/) and run `terraform init` in the `aws-vpn` directory (this directory!)\n\n### 1. Generating PKI\n\nFirst, you need to generate key material for the VPN.\nThis can be done by simply running the `pki.sh` script in the `aws-vpn` directory.\nThe certs and keys for the VPN will be placed in a `certs` folder\n\n### 2. Configuration\n\nNext, you need to configure this Terraform stack to generate a VPC/VPN/Cluster with the parameters you need.\nThe easiest way to do this is to create a `terraform.tfvars` file inside the `aws-vpn` directory and place the configuration's variables there.\nThe format of this file should be:\n\n\n```hcl\naws_region              = \"us-east-1\" # The AWS region to use\nparent_domain           = \"foo.net\" # The hosted zone mentioned in section 0\nchild_subdomain         = \"my-subdomain\" # The name of the subdomain that will be created under it.\nchild_subdomain_comment = \"My subdomain's comment\" # A human-readable description for the subdomain\nvpc_cidr                = \"10.0.0.0/16\" # The CIDR range for IP addresses within the VPC\nvpn_client_cidr         = \"10.20.0.0/22\" # The CIDR range for clients that connect to the VPN\nservice_cidr            = \"10.19.0.0/16\" # The CIDR range for k8s services in the EKS cluster\nsplit_tunnel            = true # Whether the VPN should be configured with split tunneling\n```\n\n### 3. Deploying\n\n\nNow all you have to do is apply the configuration:\n\n```bash\nterraform apply\n```\n\nTerraform will show you the infrasturcture to provision and ask for confirmation.\n\n### 4. Connecting\n\nFirst, you will have to download the VPN configuration from AWS. The following command will download it and place it in a `config.ovpn` file\n\n```bash\n# Note that you may need to pass a --region flag\naws ec2 export-client-vpn-client-configuration --client-vpn-endpoint-id $(terraform output -raw vpn_id) | jq -r .ClientConfiguration > config.ovpn\n```\n\nNote that it does not include the client cert and key, to add those simply:\n\n```bash\necho \"<cert>\" >> config.ovpn\ncat certs/VPNCert.crt >> config.ovpn\necho \"</cert>\" >> config.ovpn\necho \"<key>\" >> config.ovpn\ncat certs/VPNCert.key >> config.ovpn\necho \"</key>\" >> config.ovpn\n```\n\nAt that point, you should be able to import the `config.ovpn` file into any OpenVPN client.\n\nLastly, you'll need to download the kubernetes configuration:\n\n```bash\naws eks --region us-east-1 update-kubeconfig --name $(terraform output -raw eks_name)\n```\n\nOnce you do this, and connect to the VPN through your client, `kubectl` should be connected to the new cluster!\n"
  },
  {
    "path": "test-infra/aws-vpn/dns.tf",
    "content": "data \"aws_route53_zone\" \"parent_dns_zone\" {\n  name = var.parent_domain\n}\n\n\nresource \"aws_route53_zone\" \"child_dns_zone\" {\n  name    = \"${var.child_subdomain}.${data.aws_route53_zone.parent_dns_zone.name}\"\n  comment = var.child_subdomain_comment\n\n  tags = local.global_tags\n}\n\nresource \"aws_route53_record\" \"child_dns_route\" {\n  zone_id = data.aws_route53_zone.parent_dns_zone.id\n  name    = var.child_subdomain\n  type    = \"NS\"\n  ttl     = 3600\n  records = aws_route53_zone.child_dns_zone.name_servers\n}\n\nresource \"aws_acm_certificate\" \"vpn_server\" {\n  domain_name       = \"${local.prefix}gateway.${aws_route53_zone.child_dns_zone.name}\"\n  validation_method = \"DNS\"\n\n  tags = local.global_tags\n\n  lifecycle {\n    create_before_destroy = true\n  }\n}\n\nresource \"aws_route53_record\" \"vpn_record\" {\n  for_each = {\n    for dvo in aws_acm_certificate.vpn_server.domain_validation_options : dvo.domain_name => {\n      name   = dvo.resource_record_name\n      record = dvo.resource_record_value\n      type   = dvo.resource_record_type\n    }\n  }\n\n  allow_overwrite = true\n  name            = each.value.name\n  records         = [each.value.record]\n  ttl             = 60\n  type            = each.value.type\n  zone_id         = aws_route53_zone.child_dns_zone.zone_id\n}\n\nresource \"aws_acm_certificate_validation\" \"vpn_server\" {\n  certificate_arn = aws_acm_certificate.vpn_server.arn\n\n  depends_on = [\n    aws_route53_record.vpn_record,\n    aws_route53_record.child_dns_route,\n  ]\n\n  timeouts {\n    create = \"10m\"\n  }\n}\n"
  },
  {
    "path": "test-infra/aws-vpn/eks.tf",
    "content": "resource \"aws_security_group\" \"eks_access\" {\n  vpc_id = aws_vpc.main.id\n  name   = \"${var.child_subdomain}-${local.prefix}eks-sg\"\n\n  ingress {\n    from_port   = 443\n    protocol    = \"TCP\"\n    to_port     = 443\n    cidr_blocks = [aws_vpc.main.cidr_block, aws_ec2_client_vpn_endpoint.vpn.client_cidr_block]\n    description = \"Incoming TLS connection\"\n  }\n\n  egress {\n    from_port   = 0\n    protocol    = \"-1\"\n    to_port     = 0\n    cidr_blocks = [\"0.0.0.0/0\"]\n  }\n\n  tags = local.global_tags\n}\n\nresource \"aws_eks_cluster\" \"cluster\" {\n  name     = \"${var.child_subdomain}-${local.prefix}cluster\"\n  role_arn = aws_iam_role.cluster_role.arn\n\n  vpc_config {\n    subnet_ids              = aws_subnet.sn_az[*].id\n    endpoint_public_access  = false\n    endpoint_private_access = true\n    security_group_ids      = [aws_security_group.eks_access.id]\n  }\n\n  kubernetes_network_config {\n    service_ipv4_cidr = var.service_cidr\n  }\n\n  # Ensure that IAM Role permissions are created before and deleted after EKS Cluster handling.\n  # Otherwise, EKS will not be able to properly delete EKS managed EC2 infrastructure such as Security Groups.\n  depends_on = [\n    aws_iam_role_policy_attachment.eks_cluster_policy,\n    aws_iam_role_policy_attachment.eks_svpc_resource_controller,\n  ]\n  tags = local.global_tags\n}\n\nresource \"aws_eks_node_group\" \"node_group\" {\n  cluster_name    = aws_eks_cluster.cluster.name\n  node_group_name = \"${var.child_subdomain}-${local.prefix}node-group\"\n  node_role_arn   = aws_iam_role.node_role.arn\n  subnet_ids      = aws_subnet.sn_az[*].id\n  scaling_config {\n    desired_size = 1\n    max_size     = 1\n    min_size     = 1\n  }\n\n  update_config {\n    max_unavailable = 1\n  }\n\n  depends_on = [\n    aws_iam_role_policy_attachment.eks_worker_node,\n    aws_iam_role_policy_attachment.eks_cni,\n    aws_iam_role_policy_attachment.ec2_container_registry,\n  ]\n  tags = local.global_tags\n}\n\noutput \"eks_name\" {\n  value = aws_eks_cluster.cluster.name\n}\n\nresource \"aws_iam_role\" \"cluster_role\" {\n  name = \"${var.child_subdomain}-${local.prefix}cluster-iam\"\n  tags = local.global_tags\n\n  assume_role_policy = <<POLICY\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Service\": \"eks.amazonaws.com\"\n      },\n      \"Action\": \"sts:AssumeRole\"\n    }\n  ]\n}\nPOLICY\n}\n\nresource \"aws_iam_role_policy_attachment\" \"eks_cluster_policy\" {\n  policy_arn = \"arn:aws:iam::aws:policy/AmazonEKSClusterPolicy\"\n  role       = aws_iam_role.cluster_role.name\n}\n\n# Optionally, enable Security Groups for Pods\n# Reference: https://docs.aws.amazon.com/eks/latest/userguide/security-groups-for-pods.html\nresource \"aws_iam_role_policy_attachment\" \"eks_svpc_resource_controller\" {\n  policy_arn = \"arn:aws:iam::aws:policy/AmazonEKSVPCResourceController\"\n  role       = aws_iam_role.cluster_role.name\n}\n\nresource \"aws_iam_role\" \"node_role\" {\n  name = \"${var.child_subdomain}-${local.prefix}eks-node-role\"\n  tags = local.global_tags\n\n  assume_role_policy = jsonencode({\n    Statement = [{\n      Action = \"sts:AssumeRole\"\n      Effect = \"Allow\"\n      Principal = {\n        Service = \"ec2.amazonaws.com\"\n      }\n    }]\n    Version = \"2012-10-17\"\n  })\n}\n\nresource \"aws_iam_role_policy_attachment\" \"eks_worker_node\" {\n  policy_arn = \"arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy\"\n  role       = aws_iam_role.node_role.name\n}\n\nresource \"aws_iam_role_policy_attachment\" \"eks_cni\" {\n  policy_arn = \"arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy\"\n  role       = aws_iam_role.node_role.name\n}\n\nresource \"aws_iam_role_policy_attachment\" \"ec2_container_registry\" {\n  policy_arn = \"arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly\"\n  role       = aws_iam_role.node_role.name\n}\n"
  },
  {
    "path": "test-infra/aws-vpn/locals.tf",
    "content": "data \"aws_availability_zones\" \"available\" {\n  state = \"available\"\n}\n\nlocals {\n  global_tags = {\n    \"environment\" = \"${var.child_subdomain}-eks-vpn\"\n  }\n  availability_zones = [for x in [\"a\", \"b\", \"c\"] : \"${var.aws_region}${x}\"]\n  prefix             = \"tp-test-vpn-\"\n}\n\nvariable \"parent_domain\" {\n  type        = string\n  description = \"An already existing DNS zone to create child_dns_zone in\"\n}\n\nvariable \"child_subdomain\" {\n  type        = string\n  description = \"The prefix to a DNS zone to create. e.g. if parent_domain is 'foo.com', this should be 'bar', to create a hosted zone 'foo.bar.com'\"\n}\n\nvariable \"child_subdomain_comment\" {\n  type        = string\n  description = \"The description of the created DNS zone; will be visible in the AWS console\"\n}\n\nvariable \"aws_region\" {\n  type        = string\n  description = \"The AWS region to provision resources in\"\n}\n\nvariable \"vpc_cidr\" {\n  type        = string\n  description = \"The CIDR for the VPN\"\n}\n\nvariable \"vpn_client_cidr\" {\n  type        = string\n  description = \"The CIDR assigned to clients of the VPN\"\n}\n\nvariable \"split_tunnel\" {\n  type        = bool\n  description = \"Whether to set up split tunneling for the VPN\"\n}\n\nvariable \"service_cidr\" {\n  type        = string\n  description = \"The CIDR to put services in\"\n}\n"
  },
  {
    "path": "test-infra/aws-vpn/network.tf",
    "content": "resource \"aws_vpc\" \"main\" {\n  cidr_block = var.vpc_cidr\n\n  enable_dns_hostnames = true\n  enable_dns_support   = true\n  instance_tenancy     = \"default\"\n\n  tags = local.global_tags\n}\n\nresource \"aws_default_security_group\" \"default\" {\n  vpc_id = aws_vpc.main.id\n\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = [\"0.0.0.0/0\"]\n  }\n\n  tags = local.global_tags\n}\n\nresource \"aws_subnet\" \"sn_az\" {\n  count = length(local.availability_zones)\n\n  availability_zone = local.availability_zones[count.index]\n\n  vpc_id                  = aws_vpc.main.id\n  map_public_ip_on_launch = true\n\n  cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 5, count.index + 1)\n\n  tags = merge(local.global_tags, { \"kubernetes.io/cluster/${var.child_subdomain}-${local.prefix}cluster\" : \"shared\" })\n}\n\nresource \"aws_internet_gateway\" \"igw\" {\n  vpc_id = aws_vpc.main.id\n\n  tags = local.global_tags\n}\n\nresource \"aws_route_table\" \"rt\" {\n  vpc_id = aws_vpc.main.id\n\n  route {\n    cidr_block = \"0.0.0.0/0\"\n    gateway_id = aws_internet_gateway.igw.id\n  }\n\n  tags = local.global_tags\n}\n\nresource \"aws_route_table_association\" \"rt_assoc\" {\n  count = length(aws_subnet.sn_az)\n\n  route_table_id = aws_route_table.rt.id\n  subnet_id      = aws_subnet.sn_az[count.index].id\n}\n"
  },
  {
    "path": "test-infra/aws-vpn/pki.sh",
    "content": "#!/usr/bin/env bash\n# Script adapted from https://community.axway.com/s/question/0D52X000065Ykx2SAC/example-scripts-to-create-certificate-chain-with-openssl\n\nmkdir certs\n\nsubj='/C=CA'\n\nset -e\n\n#Generate CA Certificate\n#Generate private Key\nopenssl genrsa -out certs/CA.key 2048\n#Generate CA CSR\nopenssl req -new -sha256 -key certs/CA.key -out certs/CA.csr -subj \"$subj/CN=CA CERTIFICATE\"\n#Generate CA Certificate (10 years)\nopenssl x509 -signkey certs/CA.key -in certs/CA.csr -req -days 3650 -out certs/CA.pem\n\n#--------------------------------------------------------------------------------------\n#Generate Intermediary CA Certificate\n#Generate private Key\n\nopenssl genrsa -out certs/CA_Intermediary.key 2048\n\n#Create Intermediary CA CSR\nopenssl req -new -sha256 -key certs/CA_Intermediary.key -out certs/CA_Intermediary.csr -subj \"$subj/CN=CA INTERMEDIARY CERTIFICATE\"\n\n#Generate Server Certificate (10 years)\nopenssl x509 -req -in certs/CA_Intermediary.csr -CA certs/CA.pem -CAkey certs/CA.key -CAcreateserial -out certs/CA_Intermediary.crt -days 3650 -sha256\n\ncat certs/CA.pem certs/CA_Intermediary.crt > certs/ca-chain.crt\n \n#--------------------------------------------------------------------------------------\n#Generate VPN Certificate signed by Intermediary CA\n#Generate private Key\nopenssl genrsa -out certs/VPNCert.key 2048\n\n#Create Client CSR\nopenssl req -new -sha256 -key certs/VPNCert.key -out certs/VPNCert.csr -subj \"$subj/CN=client\"\n\n#Generate Client Certificate\nopenssl x509 -req -in certs/VPNCert.csr -CA certs/CA.pem -CAkey certs/CA.key -CAcreateserial -out certs/VPNCert.crt -days 3650 -sha256\n\n#View Certificate\nopenssl x509 -text -noout -in certs/VPNCert.crt\n"
  },
  {
    "path": "test-infra/aws-vpn/provider.tf",
    "content": "terraform {\n  required_providers {\n    aws = {\n      source  = \"hashicorp/aws\",\n      version = \"~> 3.20\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = var.aws_region\n}\n"
  },
  {
    "path": "test-infra/aws-vpn/vpn.tf",
    "content": "resource \"aws_acm_certificate\" \"vpn_client_root\" {\n  private_key       = file(\"certs/VPNCert.key\")\n  certificate_body  = file(\"certs/VPNCert.crt\")\n  certificate_chain = file(\"certs/ca-chain.crt\")\n\n  tags = local.global_tags\n}\n\nresource \"aws_security_group\" \"vpn_access\" {\n  vpc_id = aws_vpc.main.id\n  name   = \"${var.child_subdomain}-${local.prefix}-vpn-sg\"\n\n  ingress {\n    from_port   = 443\n    protocol    = \"UDP\"\n    to_port     = 443\n    cidr_blocks = [\"0.0.0.0/0\"]\n    description = \"Incoming VPN connection\"\n  }\n\n  egress {\n    from_port   = 0\n    protocol    = \"-1\"\n    to_port     = 0\n    cidr_blocks = [\"0.0.0.0/0\"]\n  }\n\n  tags = local.global_tags\n}\n\nresource \"aws_ec2_client_vpn_endpoint\" \"vpn\" {\n  description            = \"VPN endpoint for ${local.prefix}.${var.child_subdomain}.${var.parent_domain}\"\n  client_cidr_block      = var.vpn_client_cidr\n  split_tunnel           = var.split_tunnel\n  server_certificate_arn = aws_acm_certificate_validation.vpn_server.certificate_arn\n  dns_servers            = [cidrhost(var.vpc_cidr, 2)]\n\n  authentication_options {\n    type                       = \"certificate-authentication\"\n    root_certificate_chain_arn = aws_acm_certificate.vpn_client_root.arn\n  }\n\n  connection_log_options {\n    enabled = false\n  }\n\n  tags = local.global_tags\n}\n\noutput \"vpn_id\" {\n  value = aws_ec2_client_vpn_endpoint.vpn.id\n}\n\nresource \"aws_ec2_client_vpn_route\" \"internet_access\" {\n  count                  = var.split_tunnel ? 0 : length(aws_subnet.sn_az)\n  client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id\n  destination_cidr_block = \"0.0.0.0/0\"\n  # These are routed to the internet anyway via aws_route_table.rt so this will ensure that outbound traffic\n  # manages to leave.\n  target_vpc_subnet_id = aws_subnet.sn_az[count.index].id\n}\n\nresource \"aws_ec2_client_vpn_network_association\" \"vpn_subnets\" {\n  count = length(aws_subnet.sn_az)\n\n  client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id\n  subnet_id              = aws_subnet.sn_az[count.index].id\n  security_groups        = [aws_security_group.vpn_access.id]\n\n  lifecycle {\n    // The issue why we are ignoring changes is that on every change\n    // terraform screws up most of the vpn assosciations\n    // see: https://github.com/hashicorp/terraform-provider-aws/issues/14717\n    ignore_changes = [subnet_id]\n  }\n}\n\nresource \"aws_ec2_client_vpn_authorization_rule\" \"vpn_auth_rule\" {\n  client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id\n  target_network_cidr    = \"0.0.0.0/0\"\n  authorize_all_groups   = true\n}\n"
  }
]